diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0d234d8fe9b7f..859515b3ca554 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ | Q | A | ------------- | --- -| Branch? | master for features / 3.4, 4.3, 4.4 or 5.0 for bug fixes +| Branch? | master for features / 3.4, 4.4 or 5.0 for bug fixes | Bug fix? | yes/no | New feature? | yes/no | Deprecations? | yes/no @@ -11,7 +11,7 @@ Replace this notice by a short README for your feature/bugfix. This will help people understand your PR and can be used as a start for the documentation. -Additionally (see https://symfony.com/roadmap): +Additionally (see https://symfony.com/releases): - Always add tests and ensure they pass. - Never break backward compatibility (see https://symfony.com/bc). - Bug fixes must be submitted against the lowest maintained branch where they apply diff --git a/.github/patch-types.php b/.github/patch-types.php index 949b858c0f1a0..d9b1ed98f2bfe 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -18,6 +18,8 @@ case false !== strpos($file, '/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Article.php'): case false !== strpos($file, '/src/Symfony/Component/Config/Tests/Fixtures/BadFileName.php'): case false !== strpos($file, '/src/Symfony/Component/Config/Tests/Fixtures/BadParent.php'): + case false !== strpos($file, '/src/Symfony/Component/Config/Tests/Fixtures/ParseError.php'): + case false !== strpos($file, '/src/Symfony/Component/Debug/Tests/Fixtures/'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ParentNotExists.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/BadClasses/MissingParent.php'): diff --git a/.travis.yml b/.travis.yml index ac4f9c96351f3..cfc5ad7300080 100644 --- a/.travis.yml +++ b/.travis.yml @@ -308,7 +308,7 @@ install: else if [[ $PHP = 7.4* ]]; then # add return types before running the test suite - rm vendor/symfony/contracts -Rf + rm src/Symfony/Contracts -Rf && mv vendor/symfony/contracts src/Symfony/Contracts ln -sd $(realpath src/Symfony/Contracts) vendor/symfony/contracts sed -i 's/"\*\*\/Tests\/"//' composer.json composer install --optimize-autoloader diff --git a/CHANGELOG-4.4.md b/CHANGELOG-4.4.md index ada0c8529e0f5..e5f07b9d13dab 100644 --- a/CHANGELOG-4.4.md +++ b/CHANGELOG-4.4.md @@ -7,6 +7,24 @@ in 4.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.4.0...v4.4.1 +* 4.4.4 (2020-01-31) + + * bug #35530 [HttpClient] Fix regex bearer (noniagriconomie) + * bug #35532 [Validator] fix access to uninitialized property when getting value (greedyivan) + * bug #35486 [Translator] Default value for 'sort' option in translation:update should be 'asc' (versgui) + * bug #35305 [HttpKernel] Fix stale-if-error behavior, add tests (mpdude) + * bug #34808 [PhpUnitBridge] Properly handle phpunit arguments for configuration file (biozshock) + * bug #35517 [Intl] Provide more locale translations (ro0NL) + * bug #35518 [Mailer] Fix STARTTLS support for Postmark and Mandrill (fabpot) + * bug #35480 [Messenger] Check for all serialization exceptions during message dec… (Patrick Berenschot) + * bug #35502 [Messenger] Fix bug when using single route with XML config (Nyholm) + * bug #35438 [SecurityBundle] fix ldap_bind service arguments (Ioni14) + * bug #35429 [DI] CheckTypeDeclarationsPass now checks if value is type of parameter type (pfazzi) + * bug #35464 [ErrorHandler] Add debug argument to decide whether debug page is shown or not (yceruto) + * bug #35423 Fixes a runtime error when accessing the cache panel (DamienHarper) + * bug #35428 [Cache] fix checking for igbinary availability (nicolas-grekas) + * bug #35424 [HttpKernel] Check if lock can be released (sjadema) + * 4.4.3 (2020-01-21) * bug #35364 [Yaml] Throw on unquoted exclamation mark (fancyweb) diff --git a/CHANGELOG-5.0.md b/CHANGELOG-5.0.md index afdb63bf51491..f743e9945c8c3 100644 --- a/CHANGELOG-5.0.md +++ b/CHANGELOG-5.0.md @@ -7,6 +7,70 @@ in 5.0 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.0.0...v5.0.1 +* 5.0.5 (2020-02-29) + + * bug #35781 [Form] NumberToLocalizedStringTransformer return int if scale = 0 (VincentLanglet) + * bug #35846 [Serializer] prevent method calls on null values (xabbuh) + * bug #35897 [FrameworkBundle] add missing Messenger options to XML schema definition (xabbuh) + * bug #35870 [ErrorHandler] fix parsing static return type on interface method annotation (alekitto) + * bug #35839 [Security] Allow switching to another user when already switched (chalasr) + * bug #35851 [DoctrineBridge] Use new Types::* constants and support new json types (fancyweb) + * bug #35841 [Notifier] Dispatch message event in null transport (jschaedl) + * bug #35716 [PhpUnitBridge] Fix compatibility to PHPUnit 9 (Benjamin) + * bug #35803 [Cache] Fix versioned namespace atomic clears (trvrnrth) + * bug #35817 [DoctrineBridge] Use new Types::* constants and support new json type (fancyweb) + * bug #35832 [Debug][ErrorHandler] improved deprecation notices for methods new args and return type (HeahDude) + * bug #35827 [BrowserKit] Nested file array prevents uploading file (afilina) + * bug #35826 [Notifier] Add correct tags for NullTransportFactory (jschaedl) + * bug #35830 [FrameworkBundle] Skip notifiers tags in UnusedTagsPass (chalasr) + * bug #35707 [ExpressionLanguage] Fixed collisions of character operators with object properties (Andrej-in-ua) + * bug #35794 [DoctrineBridge][DoctrineExtractor] Fix indexBy with custom and some core types (fancyweb) + * bug #35787 [PhpUnitBridge] Use trait instead of extending deprecated class (marcello-moenkemeyer) + * bug #35792 [Security] Prevent TypeError in case RememberMetoken has no attached user (nikophil) + * bug #35735 [Routing] Add locale requirement for localized routes (mtarld) + * bug #35772 [Config] don't throw on missing excluded paths (nicolas-grekas) + * bug #35774 [Ldap] force default network timeout (nicolas-grekas) + * bug #35702 [VarDumper] fixed DateCaster not displaying additional fields (Makdessi Alex) + * bug #35722 [HttpKernel] Set previous exception when rethrown from controller resolver (danut007ro) + * bug #35714 [HttpClient] Correctly remove trace level options for HttpCache (aschempp) + * bug #35718 [HttpKernel] fix registering DebugHandlersListener regardless of the PHP_SAPI (nicolas-grekas) + * bug #35728 Add missing autoload calls (greg0ire) + * bug #35693 [Finder] Fix unix root dir issue (chr-hertel) + * bug #35709 [HttpFoundation] fix not sending Content-Type header for 204 responses (Tobion) + * bug #35710 [ErrorHandler] silence warning when zend.assertions=-1 (nicolas-grekas) + * bug #35676 [Console] Handle zero row count in appendRow() for Table (Adam Prickett) + * bug #35696 [Console] Don't load same-namespace alternatives on exact match (chalasr) + * bug #35674 [HttpClient] fix getting response content after its destructor throwed an HttpExceptionInterface (nicolas-grekas) + * bug #35672 [HttpClient] fix HttpClientDataCollector when handling canceled responses (thematchless) + * bug #35641 [Process] throw when PhpProcess::fromShellCommandLine() is used (Guikingone) + * bug #35645 [ErrorHandler] Never throw on warnings triggered by assert() and set assert.exception=1 in Debug::enable() (nicolas-grekas) + * bug #35633 [Mailer] Do not ping the SMTP server before sending every message (micheh) + * bug #33897 [Console] Consider STDIN interactive (ostrolucky) + * bug #35605 [HttpFoundation][FrameworkBundle] fix support for samesite in session cookies (fabpot) + * bug #35609 [DoctrineBridge] Fixed submitting ids with query limit or offset (HeahDude) + * bug #35616 [Workflow] Make method signature compatible with 4.4 (pbowyer) + * bug #35597 [PHPunit bridge] Provide current file as file path (greg0ire) + * bug #33960 [DI] Unknown env prefix not recognized as such (ro0NL) + * bug #35342 [DI] Fix support for multiple tags for locators and iterators (Alexandre Parent) + * bug #33820 [PhpUnitBridge] Fix some errors when using serialized deprecations (l-vo) + * bug #35553 Fix HTTP client config handling (julienfalque) + * bug #35588 [ErrorHandler] Escape variable in Exception template (jderusse) + * bug #35583 Add missing use statements (fabpot) + * bug #35582 Missing use statement 4.4 (fabpot) + * bug #34123 [Form] Fix handling of empty_data's \Closure value in Date/Time form types (yceruto) + * bug #35537 [Config][XmlReferenceDumper] Prevent potential \TypeError (fancyweb) + * bug #35227 [Mailer] Fix broken mandrill http send for recipients with names (vilius-g) + * bug #35430 [Translation] prefer intl domain when adding messages to catalogue (Guite) + * bug #35497 Fail on empty password verification (without warning on any implementation) (Stefan Kruppa) + * bug #35546 [Validator] check for __get method existence if property is uninitialized (alekitto) + * bug #35332 [Yaml][Inline] Fail properly on empty object tag and empty const tag (fancyweb) + * bug #35489 [PhpUnitBridge] Fix running skipped tests expecting only deprecations (chalasr) + * bug #35161 [FrameworkBundle] Check non-null type for numeric type (Arman-Hosseini) + * bug #34059 [DomCrawler] Skip disabled fields processing in Form (sbogx) + * bug #34114 [Console] SymonfyStyle - Check value isset to avoid PHP notice (leevigraham) + * bug #35557 [Config] dont catch instances of Error (nicolas-grekas) + * bug #35562 [HttpClient] fix HttpClientDataCollector when handling canceled responses (nicolas-grekas) + * 5.0.4 (2020-01-31) * bug #35530 [HttpClient] Fix regex bearer (noniagriconomie) diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index e208c6b21cd46..0d6a214c92b68 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -57,7 +57,7 @@ public function getEntitiesByIds(string $identifier, array $values) $metadata = $this->queryBuilder->getEntityManager()->getClassMetadata(current($this->queryBuilder->getRootEntities())); foreach ($this->getEntities() as $entity) { - if (\in_array(current($metadata->getIdentifierValues($entity)), $values, true)) { + if (\in_array((string) current($metadata->getIdentifierValues($entity)), $values, true)) { $choices[] = $entity; } } diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 4374ec36367b2..54c2654d7ee7d 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Form; use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as LegacyMappingException; use Doctrine\Persistence\ManagerRegistry; @@ -28,9 +29,15 @@ class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface private $cache = []; + private static $useDeprecatedConstants; + public function __construct(ManagerRegistry $registry) { $this->registry = $registry; + + if (null === self::$useDeprecatedConstants) { + self::$useDeprecatedConstants = !class_exists(Types::class); + } } /** @@ -52,13 +59,13 @@ public function guessType(string $class, string $property) } switch ($metadata->getTypeOfField($property)) { - case Type::TARRAY: - case Type::SIMPLE_ARRAY: + case self::$useDeprecatedConstants ? Type::TARRAY : Types::ARRAY: + case self::$useDeprecatedConstants ? Type::SIMPLE_ARRAY : Types::SIMPLE_ARRAY: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CollectionType', [], Guess::MEDIUM_CONFIDENCE); - case Type::BOOLEAN: + case self::$useDeprecatedConstants ? Type::BOOLEAN : Types::BOOLEAN: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CheckboxType', [], Guess::HIGH_CONFIDENCE); - case Type::DATETIME: - case Type::DATETIMETZ: + case self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE: + case self::$useDeprecatedConstants ? Type::DATETIMETZ : Types::DATETIMETZ_MUTABLE: case 'vardatetime': return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateTimeType', [], Guess::HIGH_CONFIDENCE); case 'datetime_immutable': @@ -66,25 +73,25 @@ public function guessType(string $class, string $property) return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateTimeType', ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE); case 'dateinterval': return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateIntervalType', [], Guess::HIGH_CONFIDENCE); - case Type::DATE: + case self::$useDeprecatedConstants ? Type::DATE : Types::DATE_MUTABLE: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateType', [], Guess::HIGH_CONFIDENCE); case 'date_immutable': return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateType', ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE); - case Type::TIME: + case self::$useDeprecatedConstants ? Type::TIME : Types::TIME_MUTABLE: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TimeType', [], Guess::HIGH_CONFIDENCE); case 'time_immutable': return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TimeType', ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE); - case Type::DECIMAL: + case self::$useDeprecatedConstants ? Type::DECIMAL : Types::DECIMAL: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\NumberType', ['input' => 'string'], Guess::MEDIUM_CONFIDENCE); - case Type::FLOAT: + case self::$useDeprecatedConstants ? Type::FLOAT : Types::FLOAT: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\NumberType', [], Guess::MEDIUM_CONFIDENCE); - case Type::INTEGER: - case Type::BIGINT: - case Type::SMALLINT: + case self::$useDeprecatedConstants ? Type::INTEGER : Types::INTEGER: + case self::$useDeprecatedConstants ? Type::BIGINT : Types::BIGINT: + case self::$useDeprecatedConstants ? Type::SMALLINT : Types::SMALLINT: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\IntegerType', [], Guess::MEDIUM_CONFIDENCE); - case Type::STRING: + case self::$useDeprecatedConstants ? Type::STRING : Types::STRING: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', [], Guess::MEDIUM_CONFIDENCE); - case Type::TEXT: + case self::$useDeprecatedConstants ? Type::TEXT : Types::TEXT: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextareaType', [], Guess::MEDIUM_CONFIDENCE); default: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', [], Guess::LOW_CONFIDENCE); @@ -107,7 +114,7 @@ public function guessRequired(string $class, string $property) // Check whether the field exists and is nullable or not if (isset($classMetadata->fieldMappings[$property])) { - if (!$classMetadata->isNullable($property) && Type::BOOLEAN !== $classMetadata->getTypeOfField($property)) { + if (!$classMetadata->isNullable($property) && (self::$useDeprecatedConstants ? Type::BOOLEAN : Types::BOOLEAN) !== $classMetadata->getTypeOfField($property)) { return new ValueGuess(true, Guess::HIGH_CONFIDENCE); } @@ -144,7 +151,7 @@ public function guessMaxLength(string $class, string $property) return new ValueGuess($mapping['length'], Guess::HIGH_CONFIDENCE); } - if (\in_array($ret[0]->getTypeOfField($property), [Type::DECIMAL, Type::FLOAT])) { + if (\in_array($ret[0]->getTypeOfField($property), self::$useDeprecatedConstants ? [Type::DECIMAL, Type::FLOAT] : [Types::DECIMAL, Types::FLOAT])) { return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); } } @@ -159,7 +166,7 @@ public function guessPattern(string $class, string $property) { $ret = $this->getMetadata($class); if ($ret && isset($ret[0]->fieldMappings[$property]) && !$ret[0]->hasAssociation($property)) { - if (\in_array($ret[0]->getTypeOfField($property), [Type::DECIMAL, Type::FLOAT])) { + if (\in_array($ret[0]->getTypeOfField($property), self::$useDeprecatedConstants ? [Type::DECIMAL, Type::FLOAT] : [Types::DECIMAL, Types::FLOAT])) { return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index d7de810b18068..3348011e876ba 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -277,3 +277,5 @@ public function reset() $this->choiceLoaders = []; } } + +interface_exists(ObjectManager::class); diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index e4dafdd0858be..259f25776085a 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -96,3 +96,5 @@ private function parameterToArray(Parameter $parameter): array return [$parameter->getName(), $parameter->getType(), $parameter->getValue()]; } } + +interface_exists(ObjectManager::class); diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index bc8198283e051..2a70ebb2ade98 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\PropertyInfo; use Doctrine\DBAL\Types\Type as DBALType; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; @@ -31,10 +32,15 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE { private $entityManager; private $classMetadataFactory; + private static $useDeprecatedConstants; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; + + if (null === self::$useDeprecatedConstants) { + self::$useDeprecatedConstants = !class_exists(Types::class); + } } /** @@ -103,7 +109,9 @@ public function getTypes(string $class, string $property, array $context = []) $typeOfField = $subMetadata->getTypeOfField($indexProperty); } - $collectionKeyType = $this->getPhpType($typeOfField); + if (!$collectionKeyType = $this->getPhpType($typeOfField)) { + return null; + } } } @@ -123,39 +131,47 @@ public function getTypes(string $class, string $property, array $context = []) if ($metadata->hasField($property)) { $typeOfField = $metadata->getTypeOfField($property); - $nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property); - - switch ($typeOfField) { - case DBALType::DATE: - case DBALType::DATETIME: - case DBALType::DATETIMETZ: - case 'vardatetime': - case DBALType::TIME: - return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime')]; - - case 'date_immutable': - case 'datetime_immutable': - case 'datetimetz_immutable': - case 'time_immutable': - return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTimeImmutable')]; - - case 'dateinterval': - return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateInterval')]; - case DBALType::TARRAY: - return [new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)]; + if (!$builtinType = $this->getPhpType($typeOfField)) { + return null; + } - case DBALType::SIMPLE_ARRAY: - return [new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]; + $nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property); - case DBALType::JSON_ARRAY: - return [new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)]; + switch ($builtinType) { + case Type::BUILTIN_TYPE_OBJECT: + switch ($typeOfField) { + case self::$useDeprecatedConstants ? DBALType::DATE : Types::DATE_MUTABLE: + case self::$useDeprecatedConstants ? DBALType::DATETIME : Types::DATETIME_MUTABLE: + case self::$useDeprecatedConstants ? DBALType::DATETIMETZ : Types::DATETIMETZ_MUTABLE: + case 'vardatetime': + case self::$useDeprecatedConstants ? DBALType::TIME : Types::TIME_MUTABLE: + return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime')]; + + case 'date_immutable': + case 'datetime_immutable': + case 'datetimetz_immutable': + case 'time_immutable': + return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTimeImmutable')]; + + case 'dateinterval': + return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateInterval')]; + } - default: - $builtinType = $this->getPhpType($typeOfField); + break; + case Type::BUILTIN_TYPE_ARRAY: + switch ($typeOfField) { + case self::$useDeprecatedConstants ? DBALType::TARRAY : Types::ARRAY: + case 'json_array': + case self::$useDeprecatedConstants ? false : Types::JSON: + return [new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)]; - return $builtinType ? [new Type($builtinType, $nullable)] : null; + case self::$useDeprecatedConstants ? DBALType::SIMPLE_ARRAY : Types::SIMPLE_ARRAY: + return [new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]; + } } + + return [new Type($builtinType, $nullable)]; } return null; @@ -225,29 +241,45 @@ private function isAssociationNullable(array $associationMapping): bool private function getPhpType(string $doctrineType): ?string { switch ($doctrineType) { - case DBALType::SMALLINT: - case DBALType::INTEGER: + case self::$useDeprecatedConstants ? DBALType::SMALLINT : Types::SMALLINT: + case self::$useDeprecatedConstants ? DBALType::INTEGER : Types::INTEGER: return Type::BUILTIN_TYPE_INT; - case DBALType::FLOAT: + case self::$useDeprecatedConstants ? DBALType::FLOAT : Types::FLOAT: return Type::BUILTIN_TYPE_FLOAT; - case DBALType::BIGINT: - case DBALType::STRING: - case DBALType::TEXT: - case DBALType::GUID: - case DBALType::DECIMAL: + case self::$useDeprecatedConstants ? DBALType::BIGINT : Types::BIGINT: + case self::$useDeprecatedConstants ? DBALType::STRING : Types::STRING: + case self::$useDeprecatedConstants ? DBALType::TEXT : Types::TEXT: + case self::$useDeprecatedConstants ? DBALType::GUID : Types::GUID: + case self::$useDeprecatedConstants ? DBALType::DECIMAL : Types::DECIMAL: return Type::BUILTIN_TYPE_STRING; - case DBALType::BOOLEAN: + case self::$useDeprecatedConstants ? DBALType::BOOLEAN : Types::BOOLEAN: return Type::BUILTIN_TYPE_BOOL; - case DBALType::BLOB: + case self::$useDeprecatedConstants ? DBALType::BLOB : Types::BLOB: case 'binary': return Type::BUILTIN_TYPE_RESOURCE; - case DBALType::OBJECT: + case self::$useDeprecatedConstants ? DBALType::OBJECT : Types::OBJECT: + case self::$useDeprecatedConstants ? DBALType::DATE : Types::DATE_MUTABLE: + case self::$useDeprecatedConstants ? DBALType::DATETIME : Types::DATETIME_MUTABLE: + case self::$useDeprecatedConstants ? DBALType::DATETIMETZ : Types::DATETIMETZ_MUTABLE: + case 'vardatetime': + case self::$useDeprecatedConstants ? DBALType::TIME : Types::TIME_MUTABLE: + case 'date_immutable': + case 'datetime_immutable': + case 'datetimetz_immutable': + case 'time_immutable': + case 'dateinterval': return Type::BUILTIN_TYPE_OBJECT; + + case self::$useDeprecatedConstants ? DBALType::TARRAY : Types::ARRAY: + case self::$useDeprecatedConstants ? DBALType::SIMPLE_ARRAY : Types::SIMPLE_ARRAY: + case 'json_array': + case self::$useDeprecatedConstants ? false : Types::JSON: + return Type::BUILTIN_TYPE_ARRAY; } return null; diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index 7a6c8327b5d1a..e58803b397c83 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -12,7 +12,8 @@ namespace Symfony\Bridge\Doctrine\Security\RememberMe; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Types\Type as DoctrineType; +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Types\Types; use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface; use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface; @@ -40,9 +41,15 @@ class DoctrineTokenProvider implements TokenProviderInterface { private $conn; + private static $useDeprecatedConstants; + public function __construct(Connection $conn) { $this->conn = $conn; + + if (null === self::$useDeprecatedConstants) { + self::$useDeprecatedConstants = !class_exists(Types::class); + } } /** @@ -90,7 +97,7 @@ public function updateToken(string $series, string $tokenValue, \DateTime $lastU ]; $paramTypes = [ 'value' => \PDO::PARAM_STR, - 'lastUsed' => DoctrineType::DATETIME, + 'lastUsed' => self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE, 'series' => \PDO::PARAM_STR, ]; $updated = $this->conn->executeUpdate($sql, $paramValues, $paramTypes); @@ -119,7 +126,7 @@ public function createNewToken(PersistentTokenInterface $token) 'username' => \PDO::PARAM_STR, 'series' => \PDO::PARAM_STR, 'value' => \PDO::PARAM_STR, - 'lastUsed' => DoctrineType::DATETIME, + 'lastUsed' => self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE, ]; $this->conn->executeUpdate($sql, $paramValues, $paramTypes); } diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index 01b2d647afa68..65d6bed3bcd31 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -153,3 +153,6 @@ private function getClassMetadata(): ClassMetadata return $this->getObjectManager()->getClassMetadata($this->classOrAlias); } } + +interface_exists(ObjectManager::class); +interface_exists(ObjectRepository::class); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 3454b6f1172b1..ec8f7933f9a9b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -953,7 +953,32 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleIdentifie $this->assertNull($field->getData()); } - public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleIdentifierWithLimit() + public function testSingleIdentifierWithLimit() + { + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $entity2 = new SingleIntIdEntity(2, 'Bar'); + $entity3 = new SingleIntIdEntity(3, 'Baz'); + + $this->persist([$entity1, $entity2, $entity3]); + + $repository = $this->em->getRepository(self::SINGLE_IDENT_CLASS); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, [ + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'query_builder' => $repository->createQueryBuilder('e') + ->where('e.id IN (1, 2, 3)') + ->setMaxResults(1), + 'choice_label' => 'name', + ]); + + $field->submit('1'); + + $this->assertTrue($field->isSynchronized()); + $this->assertSame($entity1, $field->getData()); + } + + public function testDisallowChoicesThatAreNotIncludedByQueryBuilderSingleIdentifierWithLimit() { $entity1 = new SingleIntIdEntity(1, 'Foo'); $entity2 = new SingleIntIdEntity(2, 'Bar'); diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index 2a65ee9b0fc14..c7dbb08a7b1ac 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -11,12 +11,16 @@ namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo; +use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Type as DBALType; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Tools\Setup; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; +use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy210; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineGeneratedValue; +use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation; use Symfony\Component\PropertyInfo\Type; /** @@ -39,27 +43,40 @@ private function createExtractor() public function testGetProperties() { + // Fields + $expected = [ + 'id', + 'guid', + 'time', + 'timeImmutable', + 'dateInterval', + 'jsonArray', + 'simpleArray', + 'float', + 'decimal', + 'bool', + 'binary', + 'customFoo', + 'bigint', + ]; + + if (class_exists(Types::class)) { + $expected[] = 'json'; + } + + // Associations + $expected = array_merge($expected, [ + 'foo', + 'bar', + 'indexedBar', + 'indexedFoo', + 'indexedByDt', + 'indexedByCustomType', + ]); + $this->assertEquals( - [ - 'id', - 'guid', - 'time', - 'timeImmutable', - 'dateInterval', - 'json', - 'simpleArray', - 'float', - 'decimal', - 'bool', - 'binary', - 'customFoo', - 'bigint', - 'foo', - 'bar', - 'indexedBar', - 'indexedFoo', - ], - $this->createExtractor()->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy') + $expected, + $this->createExtractor()->getProperties(!class_exists(Types::class) ? 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy' : DoctrineDummy210::class) ); } @@ -83,7 +100,7 @@ public function testTestGetPropertiesWithEmbedded() */ public function testExtract($property, array $type = null) { - $this->assertEquals($type, $this->createExtractor()->getTypes('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy', $property, [])); + $this->assertEquals($type, $this->createExtractor()->getTypes(!class_exists(Types::class) ? 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy' : DoctrineDummy210::class, $property, [])); } public function testExtractWithEmbedded() @@ -109,7 +126,7 @@ public function testExtractWithEmbedded() public function typesProvider() { - return [ + $provider = [ ['id', [new Type(Type::BUILTIN_TYPE_INT)]], ['guid', [new Type(Type::BUILTIN_TYPE_STRING)]], ['bigint', [new Type(Type::BUILTIN_TYPE_STRING)]], @@ -120,7 +137,7 @@ public function typesProvider() ['decimal', [new Type(Type::BUILTIN_TYPE_STRING)]], ['bool', [new Type(Type::BUILTIN_TYPE_BOOL)]], ['binary', [new Type(Type::BUILTIN_TYPE_RESOURCE)]], - ['json', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]], + ['jsonArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)]], ['foo', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation')]], ['bar', [new Type( Type::BUILTIN_TYPE_OBJECT, @@ -149,7 +166,22 @@ public function typesProvider() ['simpleArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]], ['customFoo', null], ['notMapped', null], + ['indexedByDt', [new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + Collection::class, + true, + new Type(Type::BUILTIN_TYPE_OBJECT), + new Type(Type::BUILTIN_TYPE_OBJECT, false, DoctrineRelation::class) + )]], + ['indexedByCustomType', null], ]; + + if (class_exists(Types::class)) { + $provider[] = ['json', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)]]; + } + + return $provider; } public function testGetPropertiesCatchException() diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php index d02deb15bb7b5..81264fad27c5f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php @@ -74,7 +74,7 @@ class DoctrineDummy /** * @Column(type="json_array") */ - private $json; + private $jsonArray; /** * @Column(type="simple_array") @@ -112,4 +112,14 @@ class DoctrineDummy private $bigint; public $notMapped; + + /** + * @OneToMany(targetEntity="DoctrineRelation", mappedBy="dt", indexBy="dt") + */ + protected $indexedByDt; + + /** + * @OneToMany(targetEntity="DoctrineRelation", mappedBy="customType", indexBy="customType") + */ + private $indexedByCustomType; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy210.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy210.php new file mode 100644 index 0000000000000..d3916143deab7 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy210.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\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\ManyToMany; +use Doctrine\ORM\Mapping\ManyToOne; +use Doctrine\ORM\Mapping\OneToMany; + +/** + * @Entity + */ +final class DoctrineDummy210 extends DoctrineDummy +{ + /** + * @Column(type="json", nullable=true) + */ + private $json; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php index 85660d3d6b66c..5730cf81dd493 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php @@ -39,4 +39,14 @@ class DoctrineRelation * @ManyToOne(targetEntity="DoctrineDummy", inversedBy="indexedFoo") */ protected $foo; + + /** + * @Column(type="datetime") + */ + private $dt; + + /** + * @Column(type="foo") + */ + private $customType; } diff --git a/src/Symfony/Bridge/PhpUnit/ClockMock.php b/src/Symfony/Bridge/PhpUnit/ClockMock.php index 6876e3389fde5..2cc834cd4f679 100644 --- a/src/Symfony/Bridge/PhpUnit/ClockMock.php +++ b/src/Symfony/Bridge/PhpUnit/ClockMock.php @@ -94,7 +94,7 @@ public static function register($class) { $self = \get_called_class(); - $mockedNs = array(substr($class, 0, strrpos($class, '\\'))); + $mockedNs = [substr($class, 0, strrpos($class, '\\'))]; if (0 < strpos($class, '\\Tests\\')) { $ns = str_replace('\\Tests\\', '\\', $class); $mockedNs[] = substr($ns, 0, strrpos($ns, '\\')); diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 0b14604d5a175..8ee3533064d68 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -99,7 +99,16 @@ public static function collectDeprecations($outputFile) return \call_user_func(self::getPhpUnitErrorHandler(), $type, $msg, $file, $line, $context); } - $deprecations[] = [error_reporting(), $msg, $file]; + $filesStack = []; + foreach (debug_backtrace() as $frame) { + if (!isset($frame['file']) || \in_array($frame['function'], ['require', 'require_once', 'include', 'include_once'], true)) { + continue; + } + + $filesStack[] = $frame['file']; + } + + $deprecations[] = [error_reporting(), $msg, $file, $filesStack]; return null; }); diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index 6263267fefe18..f4e9a8ff97e1d 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -44,6 +44,8 @@ class Deprecation */ private static $internalPaths = []; + private $originalFilesStack; + /** * @param string $message * @param string $file @@ -64,6 +66,7 @@ public function __construct($message, array $trace, $file) $this->message = $parsedMsg['deprecation']; $this->originClass = $parsedMsg['class']; $this->originMethod = $parsedMsg['method']; + $this->originalFilesStack = $parsedMsg['files_stack']; // If the deprecation has been triggered via // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest() // then we need to use the serialized information to determine @@ -178,14 +181,8 @@ public function getType() return self::TYPE_UNDETERMINED; } $erroringFile = $erroringPackage = null; - foreach ($this->trace as $line) { - if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) { - continue; - } - if (!isset($line['file'])) { - continue; - } - $file = $line['file']; + + foreach ($this->getOriginalFilesStack() as $file) { if ('-' === $file || 'Standard input code' === $file || !realpath($file)) { continue; } @@ -209,6 +206,22 @@ public function getType() return self::TYPE_DIRECT; } + private function getOriginalFilesStack() + { + if (null === $this->originalFilesStack) { + $this->originalFilesStack = []; + foreach ($this->trace as $frame) { + if (!isset($frame['file']) || \in_array($frame['function'], ['require', 'require_once', 'include', 'include_once'], true)) { + continue; + } + + $this->originalFilesStack[] = $frame['file']; + } + } + + return $this->originalFilesStack; + } + /** * getPathType() should always be called prior to calling this method. * diff --git a/src/Symfony/Bridge/PhpUnit/DnsMock.php b/src/Symfony/Bridge/PhpUnit/DnsMock.php index 790cfa91af5c2..a58bf2efcde35 100644 --- a/src/Symfony/Bridge/PhpUnit/DnsMock.php +++ b/src/Symfony/Bridge/PhpUnit/DnsMock.php @@ -16,8 +16,8 @@ */ class DnsMock { - private static $hosts = array(); - private static $dnsTypes = array( + private static $hosts = []; + private static $dnsTypes = [ 'A' => DNS_A, 'MX' => DNS_MX, 'NS' => DNS_NS, @@ -30,7 +30,7 @@ class DnsMock 'NAPTR' => DNS_NAPTR, 'TXT' => DNS_TXT, 'HINFO' => DNS_HINFO, - ); + ]; /** * Configures the mock values for DNS queries. @@ -68,7 +68,7 @@ public static function getmxrr($hostname, &$mxhosts, &$weight = null) if (!self::$hosts) { return \getmxrr($hostname, $mxhosts, $weight); } - $mxhosts = $weight = array(); + $mxhosts = $weight = []; if (isset(self::$hosts[$hostname])) { foreach (self::$hosts[$hostname] as $record) { @@ -125,7 +125,7 @@ public static function gethostbynamel($hostname) $ips = false; if (isset(self::$hosts[$hostname])) { - $ips = array(); + $ips = []; foreach (self::$hosts[$hostname] as $record) { if ('A' === $record['type']) { @@ -149,11 +149,11 @@ public static function dns_get_record($hostname, $type = DNS_ANY, &$authns = nul if (DNS_ANY === $type) { $type = DNS_ALL; } - $records = array(); + $records = []; foreach (self::$hosts[$hostname] as $record) { if (isset(self::$dnsTypes[$record['type']]) && (self::$dnsTypes[$record['type']] & $type)) { - $records[] = array_merge(array('host' => $hostname, 'class' => 'IN', 'ttl' => 1, 'type' => $record['type']), $record); + $records[] = array_merge(['host' => $hostname, 'class' => 'IN', 'ttl' => 1, 'type' => $record['type']], $record); } } } @@ -165,7 +165,7 @@ public static function register($class) { $self = \get_called_class(); - $mockedNs = array(substr($class, 0, strrpos($class, '\\'))); + $mockedNs = [substr($class, 0, strrpos($class, '\\'))]; if (0 < strpos($class, '\\Tests\\')) { $ns = str_replace('\\Tests\\', '\\', $class); $mockedNs[] = substr($ns, 0, strrpos($ns, '\\')); diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php index ed4128482f91f..2ce390df38609 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php @@ -23,7 +23,7 @@ class CommandForV5 extends \PHPUnit_TextUI_Command */ protected function createRunner() { - $this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : array(); + $this->arguments['listeners'] = isset($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 new file mode 100644 index 0000000000000..aa48ca5f2487a --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php @@ -0,0 +1,63 @@ + + * + * 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\TextUI\Command as BaseCommand; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\Registry; +use PHPUnit\TextUI\TestRunner as BaseRunner; +use Symfony\Bridge\PhpUnit\SymfonyTestsListener; + +/** + * {@inheritdoc} + * + * @internal + */ +class CommandForV9 extends BaseCommand +{ + /** + * {@inheritdoc} + */ + protected function createRunner(): BaseRunner + { + $this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : []; + + $registeredLocally = false; + + foreach ($this->arguments['listeners'] as $registeredListener) { + if ($registeredListener instanceof SymfonyTestsListener) { + $registeredListener->globalListenerDisabled(); + $registeredLocally = true; + break; + } + } + + if (isset($this->arguments['configuration'])) { + $configuration = $this->arguments['configuration']; + if (!$configuration instanceof Configuration) { + $configuration = Registry::getInstance()->get($this->arguments['configuration']); + } + foreach ($configuration->listeners() as $registeredListener) { + if ('Symfony\Bridge\PhpUnit\SymfonyTestsListener' === ltrim($registeredListener->className(), '\\')) { + $registeredLocally = true; + break; + } + } + } + + if (!$registeredLocally) { + $this->arguments['listeners'][] = new SymfonyTestsListener(); + } + + return parent::createRunner(); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php index 0917ea4710c21..1b3ceec161f8a 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php @@ -11,8 +11,9 @@ namespace Symfony\Bridge\PhpUnit\Legacy; -use PHPUnit\Framework\BaseTestListener; use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestListenerDefaultImplementation; /** * CoverageListener adds `@covers ` on each test when possible to @@ -22,8 +23,10 @@ * * @internal */ -class CoverageListenerForV6 extends BaseTestListener +class CoverageListenerForV6 implements TestListener { + use TestListenerDefaultImplementation; + private $trait; public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false) diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php index 6c42d3bf26439..7a64ed0d91cd5 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php @@ -32,7 +32,7 @@ public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFo { $this->sutFqcnResolver = $sutFqcnResolver; $this->warningOnSutNotFound = $warningOnSutNotFound; - $this->warnings = array(); + $this->warnings = []; } public function startTest($test) @@ -43,7 +43,7 @@ public function startTest($test) $annotations = $test->getAnnotations(); - $ignoredAnnotations = array('covers', 'coversDefaultClass', 'coversNothing'); + $ignoredAnnotations = ['covers', 'coversDefaultClass', 'coversNothing']; foreach ($ignoredAnnotations as $annotation) { if (isset($annotations['class'][$annotation]) || isset($annotations['method'][$annotation])) { @@ -70,11 +70,11 @@ public function startTest($test) $r->setAccessible(true); $cache = $r->getValue(); - $cache = array_replace_recursive($cache, array( - \get_class($test) => array( - 'covers' => \is_array($sutFqcn) ? $sutFqcn : array($sutFqcn), - ), - )); + $cache = array_replace_recursive($cache, [ + \get_class($test) => [ + 'covers' => \is_array($sutFqcn) ? $sutFqcn : [$sutFqcn], + ], + ]); $r->setValue(Test::class, $cache); } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php index a760d126ca854..9b646dca8dfab 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php @@ -22,7 +22,7 @@ class SymfonyTestsListenerForV5 extends \PHPUnit_Framework_BaseTestListener { private $trait; - public function __construct(array $mockedNamespaces = array()) + public function __construct(array $mockedNamespaces = []) { $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces); } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php index bcab4be4eeb25..8f2f6b5a7ed54 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\BaseTestListener; use PHPUnit\Framework\Test; use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; /** * Collects and replays skipped tests. @@ -27,7 +26,7 @@ class SymfonyTestsListenerForV6 extends BaseTestListener { private $trait; - public function __construct(array $mockedNamespaces = array()) + public function __construct(array $mockedNamespaces = []) { $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces); } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php index 0c2069c66bf5e..15f60a453f5be 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php @@ -15,7 +15,6 @@ use PHPUnit\Framework\TestListener; use PHPUnit\Framework\TestListenerDefaultImplementation; use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; /** * Collects and replays skipped tests. @@ -30,7 +29,7 @@ class SymfonyTestsListenerForV7 implements TestListener private $trait; - public function __construct(array $mockedNamespaces = array()) + public function __construct(array $mockedNamespaces = []) { $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces); } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index c5dcb2b0433a3..1e030825e6fde 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -35,10 +35,10 @@ class SymfonyTestsListenerTrait private static $globallyEnabled = false; private $state = -1; private $skippedFile = false; - private $wasSkipped = array(); - private $isSkipped = array(); - private $expectedDeprecations = array(); - private $gatheredDeprecations = array(); + private $wasSkipped = []; + private $isSkipped = []; + private $expectedDeprecations = []; + private $gatheredDeprecations = []; private $previousErrorHandler; private $error; private $runsInSeparateProcess = false; @@ -46,7 +46,7 @@ class SymfonyTestsListenerTrait /** * @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive or dns-sensitive) */ - public function __construct(array $mockedNamespaces = array()) + public function __construct(array $mockedNamespaces = []) { Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait'] = 2; @@ -54,7 +54,7 @@ public function __construct(array $mockedNamespaces = array()) foreach ($mockedNamespaces as $type => $namespaces) { if (!\is_array($namespaces)) { - $namespaces = array($namespaces); + $namespaces = [$namespaces]; } if ('time-sensitive' === $type) { foreach ($namespaces as $ns) { @@ -140,11 +140,11 @@ public function startTestSuite($suite) if (!$this->wasSkipped = require $this->skippedFile) { echo "All tests already ran successfully.\n"; - $suite->setTests(array()); + $suite->setTests([]); } } } - $testSuites = array($suite); + $testSuites = [$suite]; for ($i = 0; isset($testSuites[$i]); ++$i) { foreach ($testSuites[$i]->tests() as $test) { if ($test instanceof TestSuite) { @@ -163,7 +163,7 @@ public function startTestSuite($suite) } } } elseif (2 === $this->state) { - $skipped = array(); + $skipped = []; foreach ($suite->tests() as $test) { if (!($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase) || isset($this->wasSkipped[$suiteName]['*']) @@ -211,6 +211,10 @@ public function startTest($test) } } + if (!$test->getTestResultObject()) { + return; + } + $annotations = Test::parseTestMethodAnnotations(\get_class($test), $test->getName(false)); if (isset($annotations['class']['expectedDeprecation'])) { @@ -224,7 +228,7 @@ public function startTest($test) $test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false); $this->expectedDeprecations = $annotations['method']['expectedDeprecation']; - $this->previousErrorHandler = set_error_handler(array($this, 'handleError')); + $this->previousErrorHandler = set_error_handler([$this, 'handleError']); } } } @@ -247,8 +251,8 @@ public function endTest($test, $time) $deprecations = file_get_contents($this->runsInSeparateProcess); unlink($this->runsInSeparateProcess); putenv('SYMFONY_DEPRECATIONS_SERIALIZE'); - foreach ($deprecations ? unserialize($deprecations) : array() as $deprecation) { - $error = serialize(array('deprecation' => $deprecation[1], 'class' => $className, 'method' => $test->getName(false), 'triggering_file' => isset($deprecation[2]) ? $deprecation[2] : null)); + 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]); if ($deprecation[0]) { // unsilenced on purpose trigger_error($error, E_USER_DEPRECATED); @@ -260,13 +264,13 @@ public function endTest($test, $time) } if ($this->expectedDeprecations) { - if (!\in_array($test->getStatus(), array(BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE), true)) { + if (!\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE], true)) { $test->addToAssertionCount(\count($this->expectedDeprecations)); } restore_error_handler(); - if (!$errored && !\in_array($test->getStatus(), array(BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE, BaseTestRunner::STATUS_FAILURE, BaseTestRunner::STATUS_ERROR), true)) { + if (!$errored && !\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE, BaseTestRunner::STATUS_FAILURE, BaseTestRunner::STATUS_ERROR], true)) { try { $prefix = "@expectedDeprecation:\n"; $test->assertStringMatchesFormat($prefix.'%A '.implode("\n%A ", $this->expectedDeprecations)."\n%A", $prefix.' '.implode("\n ", $this->gatheredDeprecations)."\n"); @@ -275,7 +279,7 @@ public function endTest($test, $time) } } - $this->expectedDeprecations = $this->gatheredDeprecations = array(); + $this->expectedDeprecations = $this->gatheredDeprecations = []; $this->previousErrorHandler = null; } if (!$this->runsInSeparateProcess && -2 < $this->state && ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) { @@ -283,12 +287,12 @@ public function endTest($test, $time) ClockMock::withClockMock(false); } if (\in_array('dns-sensitive', $groups, true)) { - DnsMock::withMockedHosts(array()); + DnsMock::withMockedHosts([]); } } } - public function handleError($type, $msg, $file, $line, $context = array()) + public function handleError($type, $msg, $file, $line, $context = []) { if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { $h = $this->previousErrorHandler; diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php index d59b2d93372d3..fd6d059e440c2 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php @@ -14,9 +14,40 @@ 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; class DeprecationTest extends TestCase { + use SetUpTearDownTrait; + + private static $vendorDir; + + private static function getVendorDir() + { + if (null !== self::$vendorDir) { + return self::$vendorDir; + } + + foreach (get_declared_classes() as $class) { + if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $vendorDir = \dirname(\dirname($r->getFileName())); + if (file_exists($vendorDir.'/composer/installed.json') && @mkdir($vendorDir.'/myfakevendor/myfakepackage1', 0777, true)) { + break; + } + } + } + + self::$vendorDir = $vendorDir; + mkdir($vendorDir.'/myfakevendor/myfakepackage2'); + touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php'); + touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php'); + touch($vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php'); + + return self::$vendorDir; + } + public function testItCanDetermineTheClassWhereTheDeprecationHappened() { $deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__); @@ -73,12 +104,12 @@ public function mutedProvider() yield 'not from phpunit, and not a whitelisted message' => [ false, \My\Source\Code::class, - 'Self deprecating humor is deprecated by itself' + 'Self deprecating humor is deprecated by itself', ]; yield 'from phpunit, but not a whitelisted message' => [ false, \PHPUnit\Random\Piece\Of\Code::class, - 'Self deprecating humor is deprecated by itself' + 'Self deprecating humor is deprecated by itself', ]; yield 'whitelisted message, but not from phpunit' => [ false, @@ -113,17 +144,140 @@ public function testItTakesMutesDeprecationFromPhpUnitFiles() ['file' => 'should_not_matter.php'], ['file' => 'should_not_matter_either.php'], ], - 'random_path' . \DIRECTORY_SEPARATOR . 'vendor' . \DIRECTORY_SEPARATOR . 'phpunit' . \DIRECTORY_SEPARATOR . 'whatever.php' + 'random_path'.\DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR.'phpunit'.\DIRECTORY_SEPARATOR.'whatever.php' ); $this->assertTrue($deprecation->isMuted()); } + public function providerGetTypeDetectsSelf() + { + foreach (get_declared_classes() as $class) { + if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $v = \dirname(\dirname($r->getFileName())); + if (file_exists($v.'/composer/installed.json')) { + $loader = require $v.'/autoload.php'; + $reflection = new \ReflectionClass($loader); + $prop = $reflection->getProperty('prefixDirsPsr4'); + $prop->setAccessible(true); + $currentValue = $prop->getValue($loader); + $currentValue['Symfony\\Bridge\\PhpUnit\\'] = [realpath(__DIR__.'/../..')]; + $prop->setValue($loader, $currentValue); + } + } + } + + return [ + 'not_from_vendors_file' => [Deprecation::TYPE_SELF, '', 'MyClass1', __FILE__], + 'nonexistent_file' => [Deprecation::TYPE_UNDETERMINED, '', 'MyClass1', 'dummy_vendor_path'], + 'serialized_trace_with_nonexistent_triggering_file' => [ + Deprecation::TYPE_UNDETERMINED, + serialize([ + 'class' => '', + 'method' => '', + 'deprecation' => '', + 'triggering_file' => 'dummy_vendor_path', + 'files_stack' => [], + ]), + SymfonyTestsListenerForV5::class, + '', + ], + ]; + } + + /** + * @dataProvider providerGetTypeDetectsSelf + */ + public function testGetTypeDetectsSelf(string $expectedType, string $message, string $traceClass, string $file) + { + $trace = [ + ['class' => 'MyClass1', 'function' => 'myMethod'], + ['class' => $traceClass, 'function' => 'myMethod'], + ]; + $deprecation = new Deprecation($message, $trace, $file); + $this->assertSame($expectedType, $deprecation->getType()); + } + + public function providerGetTypeUsesRightTrace() + { + $vendorDir = self::getVendorDir(); + + return [ + 'no_file_in_stack' => [Deprecation::TYPE_DIRECT, '', [['function' => 'myfunc1'], ['function' => 'myfunc2']]], + 'files_in_stack_from_various_packages' => [ + Deprecation::TYPE_INDIRECT, + '', + [ + ['function' => 'myfunc1', 'file' => $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php'], + ['function' => 'myfunc2', 'file' => $vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php'], + ], + ], + 'serialized_stack_files_from_same_package' => [ + Deprecation::TYPE_DIRECT, + serialize([ + 'deprecation' => 'My deprecation message', + 'class' => 'MyClass', + 'method' => 'myMethod', + 'files_stack' => [ + $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php', + $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php', + ], + ]), + [['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']], + ], + 'serialized_stack_files_from_various_packages' => [ + Deprecation::TYPE_INDIRECT, + serialize([ + 'deprecation' => 'My deprecation message', + 'class' => 'MyClass', + 'method' => 'myMethod', + 'files_stack' => [ + $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php', + $vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php', + ], + ]), + [['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']], + ], + ]; + } + + /** + * @dataProvider providerGetTypeUsesRightTrace + */ + public function testGetTypeUsesRightTrace(string $expectedType, string $message, array $trace) + { + $deprecation = new Deprecation( + $message, + $trace, + self::getVendorDir().'/myfakevendor/myfakepackage2/MyFakeFile.php' + ); + $this->assertSame($expectedType, $deprecation->getType()); + } + /** * This method is here to simulate the extra level from the piece of code - * triggering an error to the error handler + * triggering an error to the error handler. */ - public function debugBacktrace(): array + public function debugBacktrace() { return debug_backtrace(); } + + private static function removeDir($dir) + { + $files = glob($dir.'/*'); + foreach ($files as $file) { + if (is_file($file)) { + unlink($file); + } else { + self::removeDir($file); + } + } + rmdir($dir); + } + + private static function doTearDownAfterClass() + { + self::removeDir(self::getVendorDir().'/myfakevendor'); + } } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php index 66c7684897d8b..635c43c4af06e 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DnsMockTest.php @@ -18,15 +18,15 @@ class DnsMockTest extends TestCase { protected function tearDown(): void { - DnsMock::withMockedHosts(array()); + DnsMock::withMockedHosts([]); } public function testCheckdnsrr() { - DnsMock::withMockedHosts(array('example.com' => array(array('type' => 'MX')))); + DnsMock::withMockedHosts(['example.com' => [['type' => 'MX']]]); $this->assertTrue(DnsMock::checkdnsrr('example.com')); - DnsMock::withMockedHosts(array('example.com' => array(array('type' => 'A')))); + DnsMock::withMockedHosts(['example.com' => [['type' => 'A']]]); $this->assertFalse(DnsMock::checkdnsrr('example.com')); $this->assertTrue(DnsMock::checkdnsrr('example.com', 'a')); $this->assertTrue(DnsMock::checkdnsrr('example.com', 'any')); @@ -35,34 +35,34 @@ public function testCheckdnsrr() public function testGetmxrr() { - DnsMock::withMockedHosts(array( - 'example.com' => array(array( + DnsMock::withMockedHosts([ + 'example.com' => [[ 'type' => 'MX', 'host' => 'mx.example.com', 'pri' => 10, - )), - )); + ]], + ]); $this->assertFalse(DnsMock::getmxrr('foobar.com', $mxhosts, $weight)); $this->assertTrue(DnsMock::getmxrr('example.com', $mxhosts, $weight)); - $this->assertSame(array('mx.example.com'), $mxhosts); - $this->assertSame(array(10), $weight); + $this->assertSame(['mx.example.com'], $mxhosts); + $this->assertSame([10], $weight); } public function testGethostbyaddr() { - DnsMock::withMockedHosts(array( - 'example.com' => array( - array( + DnsMock::withMockedHosts([ + 'example.com' => [ + [ 'type' => 'A', 'ip' => '1.2.3.4', - ), - array( + ], + [ 'type' => 'AAAA', 'ipv6' => '::12', - ), - ), - )); + ], + ], + ]); $this->assertSame('::21', DnsMock::gethostbyaddr('::21')); $this->assertSame('example.com', DnsMock::gethostbyaddr('::12')); @@ -71,18 +71,18 @@ public function testGethostbyaddr() public function testGethostbyname() { - DnsMock::withMockedHosts(array( - 'example.com' => array( - array( + DnsMock::withMockedHosts([ + 'example.com' => [ + [ 'type' => 'AAAA', 'ipv6' => '::12', - ), - array( + ], + [ 'type' => 'A', 'ip' => '1.2.3.4', - ), - ), - )); + ], + ], + ]); $this->assertSame('foobar.com', DnsMock::gethostbyname('foobar.com')); $this->assertSame('1.2.3.4', DnsMock::gethostbyname('example.com')); @@ -90,59 +90,59 @@ public function testGethostbyname() public function testGethostbynamel() { - DnsMock::withMockedHosts(array( - 'example.com' => array( - array( + DnsMock::withMockedHosts([ + 'example.com' => [ + [ 'type' => 'A', 'ip' => '1.2.3.4', - ), - array( + ], + [ 'type' => 'A', 'ip' => '2.3.4.5', - ), - ), - )); + ], + ], + ]); $this->assertFalse(DnsMock::gethostbynamel('foobar.com')); - $this->assertSame(array('1.2.3.4', '2.3.4.5'), DnsMock::gethostbynamel('example.com')); + $this->assertSame(['1.2.3.4', '2.3.4.5'], DnsMock::gethostbynamel('example.com')); } public function testDnsGetRecord() { - DnsMock::withMockedHosts(array( - 'example.com' => array( - array( + DnsMock::withMockedHosts([ + 'example.com' => [ + [ 'type' => 'A', 'ip' => '1.2.3.4', - ), - array( + ], + [ 'type' => 'PTR', 'ip' => '2.3.4.5', - ), - ), - )); + ], + ], + ]); - $records = array( - array( + $records = [ + [ 'host' => 'example.com', 'class' => 'IN', 'ttl' => 1, 'type' => 'A', 'ip' => '1.2.3.4', - ), - $ptr = array( + ], + $ptr = [ 'host' => 'example.com', 'class' => 'IN', 'ttl' => 1, 'type' => 'PTR', 'ip' => '2.3.4.5', - ), - ); + ], + ]; $this->assertFalse(DnsMock::dns_get_record('foobar.com')); $this->assertSame($records, DnsMock::dns_get_record('example.com')); $this->assertSame($records, DnsMock::dns_get_record('example.com', DNS_ALL)); $this->assertSame($records, DnsMock::dns_get_record('example.com', DNS_A | DNS_PTR)); - $this->assertSame(array($ptr), DnsMock::dns_get_record('example.com', DNS_PTR)); + $this->assertSame([$ptr], DnsMock::dns_get_record('example.com', DNS_PTR)); } } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/OnlyExpectingDeprecationSkippedTest.php b/src/Symfony/Bridge/PhpUnit/Tests/OnlyExpectingDeprecationSkippedTest.php new file mode 100644 index 0000000000000..593e0b4e14342 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/OnlyExpectingDeprecationSkippedTest.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\Bridge\PhpUnit\Tests; + +use PHPUnit\Framework\TestCase; + +/** + * This test is meant to be skipped. + * + * @requires extension ext-dummy + */ +final class OnlyExpectingDeprecationSkippedTest extends TestCase +{ + /** + * Do not remove this test in the next major versions. + * + * @group legacy + * + * @expectedDeprecation unreachable + */ + public function testExpectingOnlyDeprecations() + { + $this->fail('should never be ran.'); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php index be73e4d2beb5a..8690812b56b57 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php @@ -13,8 +13,10 @@ if (version_compare(\PHPUnit\Runner\Version::id(), '6.0.0', '<')) { class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV5', 'Symfony\Bridge\PhpUnit\TextUI\Command'); -} else { +} elseif (version_compare(\PHPUnit\Runner\Version::id(), '9.0.0', '<')) { class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV6', 'Symfony\Bridge\PhpUnit\TextUI\Command'); +} else { + class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV9', 'Symfony\Bridge\PhpUnit\TextUI\Command'); } if (false) { diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php index 6bb2c292e7777..401aafde162a4 100644 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php @@ -15,7 +15,7 @@ error_reporting(-1); global $argv, $argc; -$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array(); +$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : []; $argc = isset($_SERVER['argc']) ? $_SERVER['argc'] : 0; $getEnvVar = function ($name, $default = false) use ($argv) { if (false !== $value = getenv($name)) { @@ -130,11 +130,11 @@ $PHP .= ' -qrr'; } -$defaultEnvs = array( +$defaultEnvs = [ 'COMPOSER' => 'composer.json', 'COMPOSER_VENDOR_DIR' => 'vendor', 'COMPOSER_BIN_DIR' => 'bin', -); +]; foreach ($defaultEnvs as $envName => $envValue) { if ($envValue !== getenv($envName)) { @@ -147,21 +147,21 @@ || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar 2> /dev/null`)) || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer`) : `which composer 2> /dev/null`)) || file_exists($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? `git rev-parse --show-toplevel 2> NUL` : `git rev-parse --show-toplevel 2> /dev/null`).DIRECTORY_SEPARATOR.'composer.phar') - ? (file_get_contents($COMPOSER, false, null, 0, 18) === '#!/usr/bin/env php' ? $PHP : '').' '.escapeshellarg($COMPOSER) // detect shell wrappers by looking at the shebang + ? ('#!/usr/bin/env php' === file_get_contents($COMPOSER, false, null, 0, 18) ? $PHP : '').' '.escapeshellarg($COMPOSER) // detect shell wrappers by looking at the shebang : 'composer'; -$SYMFONY_PHPUNIT_REMOVE = $getEnvVar('SYMFONY_PHPUNIT_REMOVE', 'phpspec/prophecy'.($PHPUNIT_VERSION < 6.0 ? ' symfony/yaml': '')); -$configurationHash = md5(implode(PHP_EOL, array(md5_file(__FILE__), $SYMFONY_PHPUNIT_REMOVE, (int) $PHPUNIT_REMOVE_RETURN_TYPEHINT))); -$PHPUNIT_VERSION_DIR=sprintf('phpunit-%s-%d', $PHPUNIT_VERSION, $PHPUNIT_REMOVE_RETURN_TYPEHINT); +$SYMFONY_PHPUNIT_REMOVE = $getEnvVar('SYMFONY_PHPUNIT_REMOVE', 'phpspec/prophecy'.($PHPUNIT_VERSION < 6.0 ? ' symfony/yaml' : '')); +$configurationHash = md5(implode(PHP_EOL, [md5_file(__FILE__), $SYMFONY_PHPUNIT_REMOVE, (int) $PHPUNIT_REMOVE_RETURN_TYPEHINT])); +$PHPUNIT_VERSION_DIR = sprintf('phpunit-%s-%d', $PHPUNIT_VERSION, $PHPUNIT_REMOVE_RETURN_TYPEHINT); if (!file_exists("$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit") || $configurationHash !== @file_get_contents("$PHPUNIT_DIR/.$PHPUNIT_VERSION_DIR.md5")) { // Build a standalone phpunit without symfony/yaml nor prophecy by default @mkdir($PHPUNIT_DIR, 0777, true); chdir($PHPUNIT_DIR); if (file_exists("$PHPUNIT_VERSION_DIR")) { - passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s > NUL': 'rm -rf %s', "$PHPUNIT_VERSION_DIR.old")); + passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s > NUL' : 'rm -rf %s', "$PHPUNIT_VERSION_DIR.old")); rename("$PHPUNIT_VERSION_DIR", "$PHPUNIT_VERSION_DIR.old"); - passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s': 'rm -rf %s', "$PHPUNIT_VERSION_DIR.old")); + passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s' : 'rm -rf %s', "$PHPUNIT_VERSION_DIR.old")); } $passthruOrFail("$COMPOSER create-project --no-install --prefer-dist --no-scripts --no-plugins --no-progress --ansi phpunit/phpunit $PHPUNIT_VERSION_DIR \"$PHPUNIT_VERSION.*\""); @copy("$PHPUNIT_VERSION_DIR/phpunit.xsd", 'phpunit.xsd'); @@ -187,7 +187,7 @@ putenv("COMPOSER_ROOT_VERSION=$PHPUNIT_VERSION.99"); $q = '\\' === DIRECTORY_SEPARATOR ? '"' : ''; // --no-suggest is not in the list to keep compat with composer 1.0, which is shipped with Ubuntu 16.04LTS - $exit = proc_close(proc_open("$q$COMPOSER install --no-dev --prefer-dist --no-progress --ansi$q", array(), $p, getcwd())); + $exit = proc_close(proc_open("$q$COMPOSER install --no-dev --prefer-dist --no-progress --ansi$q", [], $p, getcwd())); putenv('COMPOSER_ROOT_VERSION'.(false !== $prevRoot ? '='.$prevRoot : '')); if ($exit) { exit($exit); @@ -233,13 +233,20 @@ class SymfonyBlacklistPhpunit {} } if ($PHPUNIT_VERSION < 8.0) { - $argv = array_filter($argv, function ($v) use (&$argc) { if ('--do-not-cache-result' !== $v) return true; --$argc; return false; }); + $argv = array_filter($argv, function ($v) use (&$argc) { + if ('--do-not-cache-result' !== $v) { + return true; + } + --$argc; + + return false; + }); } elseif (filter_var(getenv('SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE'), FILTER_VALIDATE_BOOLEAN)) { $argv[] = '--do-not-cache-result'; ++$argc; } -$components = array(); +$components = []; $cmd = array_map('escapeshellarg', $argv); $exit = 0; @@ -274,7 +281,7 @@ class SymfonyBlacklistPhpunit {} if ($components) { $skippedTests = isset($_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS']) ? $_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS'] : false; - $runningProcs = array(); + $runningProcs = []; foreach ($components as $component) { // Run phpunit tests in parallel @@ -285,7 +292,7 @@ class SymfonyBlacklistPhpunit {} $c = escapeshellarg($component); - if ($proc = proc_open(sprintf($cmd, $c, " > $c/phpunit.stdout 2> $c/phpunit.stderr"), array(), $pipes)) { + if ($proc = proc_open(sprintf($cmd, $c, " > $c/phpunit.stdout 2> $c/phpunit.stderr"), [], $pipes)) { $runningProcs[$component] = $proc; } else { $exit = 1; @@ -295,7 +302,7 @@ class SymfonyBlacklistPhpunit {} while ($runningProcs) { usleep(300000); - $terminatedProcs = array(); + $terminatedProcs = []; foreach ($runningProcs as $component => $proc) { $procStatus = proc_get_status($proc); if (!$procStatus['running']) { @@ -306,7 +313,7 @@ class SymfonyBlacklistPhpunit {} } foreach ($terminatedProcs as $component => $procStatus) { - foreach (array('out', 'err') as $file) { + foreach (['out', 'err'] as $file) { $file = "$component/phpunit.std$file"; readfile($file); unlink($file); @@ -316,7 +323,7 @@ class SymfonyBlacklistPhpunit {} // STATUS_STACK_BUFFER_OVERRUN (-1073740791/0xC0000409) // STATUS_ACCESS_VIOLATION (-1073741819/0xC0000005) // STATUS_HEAP_CORRUPTION (-1073740940/0xC0000374) - if ($procStatus && ('\\' !== DIRECTORY_SEPARATOR || !extension_loaded('apcu') || !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) || !in_array($procStatus, array(-1073740791, -1073741819, -1073740940)))) { + if ($procStatus && ('\\' !== DIRECTORY_SEPARATOR || !extension_loaded('apcu') || !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) || !in_array($procStatus, [-1073740791, -1073741819, -1073740940]))) { $exit = $procStatus; echo "\033[41mKO\033[0m $component\n\n"; } else { @@ -326,9 +333,11 @@ class SymfonyBlacklistPhpunit {} } } elseif (!isset($argv[1]) || 'install' !== $argv[1] || file_exists('install')) { if (!class_exists('SymfonyBlacklistSimplePhpunit', false)) { - class SymfonyBlacklistSimplePhpunit {} + class SymfonyBlacklistSimplePhpunit + { + } } - array_splice($argv, 1, 0, array('--colors=always')); + array_splice($argv, 1, 0, ['--colors=always']); $_SERVER['argv'] = $argv; $_SERVER['argc'] = ++$argc; include "$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit"; diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index 017793ee4920b..9846aa970c03d 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -24,7 +24,7 @@ "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" }, "conflict": { - "phpunit/phpunit": "<5.4.3" + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0" }, "autoload": { "files": [ "bootstrap.php" ], diff --git a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php index bc5f24ba7a804..d149c626a0027 100644 --- a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php +++ b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php @@ -35,7 +35,7 @@ class TwigErrorRenderer implements ErrorRendererInterface public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, $debug = false) { if (!\is_bool($debug) && !\is_callable($debug)) { - throw new \TypeError(sprintf('Argument 2 passed to %s() must be a boolean or a callable, %s given.', __METHOD__, \is_object($debug) ? \get_class($debug) : \gettype($debug))); + throw new \TypeError(sprintf('Argument 3 passed to %s() must be a boolean or a callable, %s given.', __METHOD__, \is_object($debug) ? \get_class($debug) : \gettype($debug))); } $this->twig = $twig; diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index b5118b7f08c7b..7329163e17bcf 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -41,12 +41,17 @@ class NotificationEmail extends TemplatedEmail public function __construct(Headers $headers = null, AbstractPart $body = null) { + $missingPackages = []; if (!class_exists(CssInlinerExtension::class)) { - throw new \LogicException(sprintf('You cannot use "%s" if the CSS Inliner Twig extension is not available; try running "composer require twig/cssinliner-extra".', static::class)); + $missingPackages['twig/cssinliner-extra'] = ' CSS Inliner'; } if (!class_exists(InkyExtension::class)) { - throw new \LogicException(sprintf('You cannot use "%s" if the Inky Twig extension is not available; try running "composer require twig/inky-extra".', static::class)); + $missingPackages['twig/inky-extra'] = 'Inky'; + } + + if ($missingPackages) { + throw new \LogicException(sprintf('You cannot use "%s" if the %s Twig extension%s not available; try running "composer require %s".', static::class, implode(' and ', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', implode(' ', array_keys($missingPackages)))); } parent::__construct($headers, $body); diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php index e94f1b7189f69..16718e8a75986 100644 --- a/src/Symfony/Bridge/Twig/Node/DumpNode.php +++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php @@ -32,9 +32,6 @@ public function __construct($varPrefix, ?Node $values, int $lineno, string $tag $this->varPrefix = $varPrefix; } - /** - * @return void - */ public function compile(Compiler $compiler): void { $compiler diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index d8a791b3a40b8..55bc3ae9a8959 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -107,8 +107,6 @@ protected function doLeaveNode(Node $node, Environment $env): ?Node /** * {@inheritdoc} - * - * @return int */ public function getPriority(): int { diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php index 2764afc0a402c..89a15cd622c5d 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php @@ -97,8 +97,6 @@ protected function doLeaveNode(Node $node, Environment $env): ?Node /** * {@inheritdoc} - * - * @return int */ public function getPriority(): int { diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index d3548430b9f6b..4818d379312a3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -79,7 +79,7 @@ public function setContainer(ContainerInterface $container): ?ContainerInterface protected function getParameter(string $name) { if (!$this->container->has('parameter_bag')) { - throw new ServiceNotFoundException('parameter_bag', null, null, [], sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', \get_class($this))); + throw new ServiceNotFoundException('parameter_bag', null, null, [], sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', static::class)); } return $this->container->get('parameter_bag')->get($name); diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php index 0fff40bac58ea..fded9ced62946 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php @@ -50,13 +50,13 @@ public function templateAction(string $template, int $maxAge = null, int $shared $response->setMaxAge($maxAge); } - if ($sharedAge) { + if (null !== $sharedAge) { $response->setSharedMaxAge($sharedAge); } if ($private) { $response->setPrivate(); - } elseif (false === $private || (null === $private && ($maxAge || $sharedAge))) { + } elseif (false === $private || (null === $private && (null !== $maxAge || null !== $sharedAge))) { $response->setPublic(); } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 5bbcb7452e73d..89466be362d13 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -23,14 +23,21 @@ class UnusedTagsPass implements CompilerPassInterface { private $whitelist = [ 'annotations.cached_reader', + 'auto_alias', + 'cache.pool', 'cache.pool.clearer', + 'chatter.transport_factory', + 'config_cache.resource_checker', 'console.command', + 'container.env_var_loader', + 'container.env_var_processor', 'container.hot_path', 'container.reversible', 'container.service_locator', + 'container.service_locator_context', 'container.service_subscriber', + 'controller.argument_value_resolver', 'controller.service_arguments', - 'config_cache.resource_checker', 'data_collector', 'form.type', 'form.type_extension', @@ -42,11 +49,19 @@ class UnusedTagsPass implements CompilerPassInterface 'kernel.event_subscriber', 'kernel.fragment_renderer', 'kernel.locale_aware', + 'kernel.reset', + 'mailer.transport_factory', 'messenger.bus', - 'messenger.receiver', 'messenger.message_handler', + 'messenger.receiver', + 'messenger.transport_factory', 'mime.mime_type_guesser', 'monolog.logger', + 'notifier.channel', + 'property_info.access_extractor', + 'property_info.initializable_extractor', + 'property_info.list_extractor', + 'property_info.type_extractor', 'proxy', 'routing.expression_language_provider', 'routing.loader', @@ -56,14 +71,16 @@ class UnusedTagsPass implements CompilerPassInterface 'security.voter', 'serializer.encoder', 'serializer.normalizer', + 'texter.transport_factory', 'translation.dumper', 'translation.extractor', 'translation.loader', 'twig.extension', 'twig.loader', + 'twig.runtime', + 'validator.auto_mapper', 'validator.constraint_validator', 'validator.initializer', - 'validator.auto_mapper', ]; public function process(ContainerBuilder $container) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index f4bddfd843d8b..228b62727c697 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1265,7 +1265,7 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode) if (!\is_array($config)) { return []; } - if (!isset($config['host'])) { + if (!isset($config['host'], $config['value']) || \count($config) > 2) { return $config; } @@ -1374,7 +1374,7 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode) if (!\is_array($config)) { return []; } - if (!isset($config['key'])) { + if (!isset($config['key'], $config['value']) || \count($config) > 2) { return $config; } @@ -1404,7 +1404,7 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode) if (!\is_array($config)) { return []; } - if (!isset($config['host'])) { + if (!isset($config['host'], $config['value']) || \count($config) > 2) { return $config; } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 9d356e5aed31e..005086436e80a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -392,8 +392,6 @@ public function load(array $configs, ContainerBuilder $container) ->addTag('controller.argument_value_resolver'); $container->registerForAutoconfiguration(AbstractController::class) ->addTag('controller.service_arguments'); - $container->registerForAutoconfiguration('Symfony\Bundle\FrameworkBundle\Controller\Controller') - ->addTag('controller.service_arguments'); $container->registerForAutoconfiguration(DataCollectorInterface::class) ->addTag('data_collector'); $container->registerForAutoconfiguration(FormTypeInterface::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/bin/check-unused-tags-whitelist.php b/src/Symfony/Bundle/FrameworkBundle/Resources/bin/check-unused-tags-whitelist.php new file mode 100644 index 0000000000000..7f24973cd8f14 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/bin/check-unused-tags-whitelist.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require dirname(__DIR__, 6).'/vendor/autoload.php'; + +use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\UnusedTagsPassUtils; + +$target = dirname(__DIR__, 2).'/DependencyInjection/Compiler/UnusedTagsPass.php'; +$contents = file_get_contents($target); +$contents = preg_replace('{private \$whitelist = \[(.+?)\];}sm', "private \$whitelist = [\n '".implode("',\n '", UnusedTagsPassUtils::getDefinedTags())."',\n ];", $contents); +file_put_contents($target, $contents); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.xml index c4d9cf892adca..2b7d14b25b6f7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.xml @@ -27,7 +27,8 @@ - + + 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 95ed47972093c..84151a2099a92 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 @@ -362,6 +362,7 @@ + @@ -413,6 +414,7 @@ + @@ -443,12 +445,21 @@ + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php index 1377a62882494..ae4236a01b71a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php @@ -31,4 +31,27 @@ public function testProcess() $this->assertSame([sprintf('%s: Tag "kenrel.event_subscriber" was defined on service(s) "foo", "bar", but was never used. Did you mean "kernel.event_subscriber"?', UnusedTagsPass::class)], $container->getCompiler()->getLog()); } + + public function testMissingWhitelistTags() + { + if (\dirname((new \ReflectionClass(ContainerBuilder::class))->getFileName(), 3) !== \dirname(__DIR__, 5)) { + $this->markTestSkipped('Tests are not run from the root symfony/symfony metapackage.'); + } + + $this->assertSame(UnusedTagsPassUtils::getDefinedTags(), $this->getWhitelistTags(), 'The src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php file must be updated; run src/Symfony/Bundle/FrameworkBundle/Resources/bin/check-unused-tags-whitelist.php.'); + } + + private function getWhitelistTags(): array + { + // get tags in UnusedTagsPass + $target = \dirname(__DIR__, 3).'/DependencyInjection/Compiler/UnusedTagsPass.php'; + $contents = file_get_contents($target); + preg_match('{private \$whitelist = \[(.+?)\];}sm', $contents, $matches); + $tags = array_values(array_filter(array_map(function ($str) { + return trim(preg_replace('{^ +\'(.+)\',}', '$1', $str)); + }, explode("\n", $matches[1])))); + sort($tags); + + return $tags; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassUtils.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassUtils.php new file mode 100644 index 0000000000000..67c97263ccdfd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassUtils.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\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use Symfony\Component\Finder\Finder; + +class UnusedTagsPassUtils +{ + public static function getDefinedTags(): array + { + $tags = [ + 'proxy' => true, + ]; + + // get all tags used in XML configs + $files = Finder::create()->files()->name('*.xml')->path('Resources')->notPath('Tests')->in(\dirname(__DIR__, 5)); + foreach ($files as $file) { + $contents = file_get_contents($file); + if (preg_match_all('{files()->name('*.php')->path('DependencyInjection')->notPath('Tests')->in(\dirname(__DIR__, 5)); + foreach ($files as $file) { + $contents = file_get_contents($file); + if (preg_match_all('{findTaggedServiceIds\(\'([^\']+)\'}', $contents, $matches)) { + foreach ($matches[1] as $match) { + if ('my.tag' === $match) { + continue; + } + $tags[$match] = true; + } + } + if (preg_match_all('{findTaggedServiceIds\(\$this->([^,\)]+)}', $contents, $matches)) { + foreach ($matches[1] as $var) { + if (preg_match_all('{\$'.$var.' = \'([^\']+)\'}', $contents, $matches)) { + foreach ($matches[1] as $match) { + $tags[$match] = true; + } + } + } + } + } + + $tags = array_keys($tags); + sort($tags); + + return $tags; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_xml_key.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_xml_key.php new file mode 100644 index 0000000000000..64778c61561b6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_xml_key.php @@ -0,0 +1,22 @@ +loadFromExtension('framework', [ + 'http_client' => [ + 'default_options' => [ + 'resolve' => [ + 'host' => '127.0.0.1', + ], + ], + 'scoped_clients' => [ + 'foo' => [ + 'base_uri' => 'http://example.com', + 'query' => [ + 'key' => 'foo', + ], + 'resolve' => [ + 'host' => '127.0.0.1', + ], + ], + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php index 68ff3607465b2..0aff440e855e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php @@ -3,6 +3,7 @@ $container->loadFromExtension('framework', [ 'serializer' => true, 'messenger' => [ + 'failure_transport' => 'failed', 'serializer' => [ 'default_serializer' => 'messenger.transport.symfony_serializer', ], @@ -12,7 +13,14 @@ 'dsn' => 'amqp://localhost/%2f/messages?exchange_name=exchange_name', 'options' => ['queue' => ['name' => 'Queue']], 'serializer' => 'messenger.transport.native_php_serializer', + 'retry_strategy' => [ + 'max_retries' => 10, + 'delay' => 7, + 'multiplier' => 3, + 'max_delay' => 100, + ], ], + 'failed' => 'in-memory:///', 'redis' => 'redis://127.0.0.1:6379/messages', ], ], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_xml_key.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_xml_key.xml new file mode 100644 index 0000000000000..95ef0737f8a02 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_xml_key.xml @@ -0,0 +1,19 @@ + + + + + + + 127.0.0.1 + + + foo + 127.0.0.1 + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml index bb698cbc17105..837db14c1cad4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml @@ -7,7 +7,7 @@ - + @@ -16,7 +16,9 @@ Queue + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_xml_key.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_xml_key.yml new file mode 100644 index 0000000000000..dc87555a901ae --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_xml_key.yml @@ -0,0 +1,12 @@ +framework: + http_client: + default_options: + resolve: + host: 127.0.0.1 + scoped_clients: + foo: + base_uri: http://example.com + query: + key: foo + resolve: + host: 127.0.0.1 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml index 2fc1f482653e4..daab75bd87e40 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml @@ -1,6 +1,7 @@ framework: serializer: true messenger: + failure_transport: failed serializer: default_serializer: messenger.transport.symfony_serializer transports: @@ -11,4 +12,10 @@ framework: queue: name: Queue serializer: 'messenger.transport.native_php_serializer' + retry_strategy: + max_retries: 10 + delay: 7 + multiplier: 3 + max_delay: 100 + failed: 'in-memory:///' redis: 'redis://127.0.0.1:6379/messages' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index c12cf7d148c26..657b67e8f0bed 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -627,6 +627,13 @@ public function testMessengerTransports() $this->assertSame('redis://127.0.0.1:6379/messages', $transportArguments[0]); $this->assertTrue($container->hasDefinition('messenger.transport.redis.factory')); + + $this->assertSame(10, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(0)); + $this->assertSame(7, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(1)); + $this->assertSame(3, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(2)); + $this->assertSame(100, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(3)); + + $this->assertEquals(new Reference('messenger.transport.failed'), $container->getDefinition('messenger.failure.send_failed_message_to_failure_transport_listener')->getArgument(0)); } public function testMessengerRouting() @@ -1358,6 +1365,21 @@ public function testHttpClientOverrideDefaultOptions() $this->assertSame($expected, $container->getDefinition('foo')->getArgument(2)); } + public function testHttpClientWithQueryParameterKey() + { + $container = $this->createContainerFromFile('http_client_xml_key'); + + $expected = [ + 'key' => 'foo', + ]; + $this->assertSame($expected, $container->getDefinition('foo')->getArgument(2)['query']); + + $expected = [ + 'host' => '127.0.0.1', + ]; + $this->assertSame($expected, $container->getDefinition('foo')->getArgument(2)['resolve']); + } + public function testHttpClientFullDefaultOptions() { $container = $this->createContainerFromFile('http_client_full_default_options'); @@ -1418,7 +1440,7 @@ protected function createContainer(array $data = []) protected function createContainerFromFile($file, $data = [], $resetCompilerPasses = true, $compile = true) { - $cacheKey = md5(\get_class($this).$file.serialize($data)); + $cacheKey = md5(static::class.$file.serialize($data)); if ($compile && isset(self::$containerCache[$cacheKey])) { return self::$containerCache[$cacheKey]; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php index 0b1cc7e65fd56..9bf91ec213da8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php @@ -69,6 +69,6 @@ protected static function createKernel(array $options = []): KernelInterface protected static function getVarDir() { - return 'FB'.substr(strrchr(\get_called_class(), '\\'), 1); + return 'FB'.substr(strrchr(static::class, '\\'), 1); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php index fd7fe7158bf78..6b960bb0274f0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php @@ -109,7 +109,7 @@ public function testTransWithCachingWithInvalidLocale() public function testLoadResourcesWithoutCaching() { - $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); + $loader = new YamlFileLoader(); $resourceFiles = [ 'fr' => [ __DIR__.'/../Fixtures/Resources/translations/messages.fr.yml', @@ -186,7 +186,7 @@ public function getDebugModeAndCacheDirCombinations() public function testCatalogResourcesAreAddedForScannedDirectories() { - $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); + $loader = new YamlFileLoader(); $resourceFiles = [ 'fr' => [ __DIR__.'/../Fixtures/Resources/translations/messages.fr.yml', @@ -348,7 +348,7 @@ public function getTranslator($loader, $options = [], $loaderFomat = 'loader', $ public function testWarmup() { - $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); + $loader = new YamlFileLoader(); $resourceFiles = [ 'fr' => [ __DIR__.'/../Fixtures/Resources/translations/messages.fr.yml', @@ -373,7 +373,7 @@ public function testWarmup() public function testLoadingTranslationFilesWithDotsInMessageDomain() { - $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); + $loader = new YamlFileLoader(); $resourceFiles = [ 'en' => [ __DIR__.'/../Fixtures/Resources/translations/domain.with.dots.en.yml', diff --git a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php index fa651fd588b3d..427ee1edf2b89 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php @@ -82,16 +82,16 @@ protected function configure() Pass the full user class path as the second argument to encode passwords for your own entities: - php %command.full_name% --no-interaction [password] App\Entity\User + php %command.full_name% --no-interaction [password] 'App\Entity\User' Executing the command interactively allows you to generate a random salt for encoding the password: - php %command.full_name% [password] App\Entity\User + php %command.full_name% [password] 'App\Entity\User' In case your encoder doesn't require a salt, add the empty-salt option: - php %command.full_name% --empty-salt [password] App\Entity\User + php %command.full_name% --empty-salt [password] 'App\Entity\User' EOF ) diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php index 67b178f3f6927..8e06d6ed1eeb0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php @@ -15,7 +15,6 @@ use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Exception\LazyResponseException; use Symfony\Component\Security\Http\Firewall\AbstractListener; -use Symfony\Component\VarDumper\Caster\ClassStub; /** * Wraps a lazy security listener. diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php index d42a878c9dacd..bce3d265560a5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php @@ -12,7 +12,6 @@ namespace Symfony\Bundle\SecurityBundle\Debug; use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\VarDumper\Caster\ClassStub; /** * Wraps a security listener for calls record. diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index 59b79f354766f..95e3e6e345306 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -424,7 +424,7 @@ public function testEncodersWithArgon2i() ], 'JMS\FooBundle\Entity\User7' => [ 'class' => $sodium ? SodiumPasswordEncoder::class : NativePasswordEncoder::class, - 'arguments' => $sodium ? [256, 1] : [1, 262144, null, \PASSWORD_ARGON2I], + 'arguments' => $sodium ? [256, 1] : [1, 262144, null, PASSWORD_ARGON2I], ], ]], $container->getDefinition('security.encoder_factory.generic')->getArguments()); } @@ -534,7 +534,7 @@ public function testEncodersWithBCrypt() ], 'JMS\FooBundle\Entity\User7' => [ 'class' => NativePasswordEncoder::class, - 'arguments' => [null, null, 15, \PASSWORD_BCRYPT], + 'arguments' => [null, null, 15, PASSWORD_BCRYPT], ], ]], $container->getDefinition('security.encoder_factory.generic')->getArguments()); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php index 010b6219d8f9e..643cb1c40d2eb 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php @@ -69,6 +69,6 @@ protected static function createKernel(array $options = []): KernelInterface protected static function getVarDir() { - return 'SB'.substr(strrchr(\get_called_class(), '\\'), 1); + return 'SB'.substr(strrchr(static::class, '\\'), 1); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php index d302b63791076..8336dce245792 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php @@ -12,7 +12,6 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle; use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php index 0ccacf583fe57..183b1ad8c4ef8 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php @@ -29,15 +29,15 @@ public function testSwitchUser($originalUser, $targetUser, $expectedUser, $expec $this->assertEquals($expectedUser, $client->getProfile()->getCollector('security')->getUser()); } - public function testSwitchedUserCannotSwitchToOther() + public function testSwitchedUserCanSwitchToOther() { $client = $this->createAuthenticatedClient('user_can_switch'); $client->request('GET', '/profile?_switch_user=user_cannot_switch_1'); $client->request('GET', '/profile?_switch_user=user_cannot_switch_2'); - $this->assertEquals(500, $client->getResponse()->getStatusCode()); - $this->assertEquals('user_cannot_switch_1', $client->getProfile()->getCollector('security')->getUser()); + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + $this->assertEquals('user_cannot_switch_2', $client->getProfile()->getCollector('security')->getUser()); } public function testSwitchedUserExit() diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 6465faa6e0bca..546b9361b10db 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -24,7 +24,7 @@ "symfony/security-core": "^4.4|^5.0", "symfony/security-csrf": "^4.4|^5.0", "symfony/security-guard": "^4.4|^5.0", - "symfony/security-http": "^4.4.3|^5.0.3" + "symfony/security-http": "^4.4.5|^5.0.5" }, "require-dev": { "doctrine/doctrine-bundle": "^2.0", diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index 062d843cd0a73..6c955aae6fa50 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -11,7 +11,6 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; -use Symfony\Bridge\Twig\Extension\AssetExtension; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig index c085303213897..4e4a59d1b3ce4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig @@ -153,7 +153,7 @@ {{ collector.symfonyeom }} {{ collector.symfonyeol }} - View roadmap + View roadmap diff --git a/src/Symfony/Component/Asset/Tests/Context/RequestStackContextTest.php b/src/Symfony/Component/Asset/Tests/Context/RequestStackContextTest.php index 7f24534eba202..d9a54c066ec3e 100644 --- a/src/Symfony/Component/Asset/Tests/Context/RequestStackContextTest.php +++ b/src/Symfony/Component/Asset/Tests/Context/RequestStackContextTest.php @@ -37,7 +37,7 @@ public function testGetBasePathSet() $requestStackContext = new RequestStackContext($requestStack); - $this->assertEquals($testBasePath, $requestStackContext->getBasePath()); + $this->assertSame($testBasePath, $requestStackContext->getBasePath()); } public function testIsSecureFalse() diff --git a/src/Symfony/Component/Asset/Tests/PackageTest.php b/src/Symfony/Component/Asset/Tests/PackageTest.php index 8f6626ae4d2ad..8d7f7b8a26a71 100644 --- a/src/Symfony/Component/Asset/Tests/PackageTest.php +++ b/src/Symfony/Component/Asset/Tests/PackageTest.php @@ -24,7 +24,7 @@ class PackageTest extends TestCase public function testGetUrl($version, $format, $path, $expected) { $package = new Package($version ? new StaticVersionStrategy($version, $format) : new EmptyVersionStrategy()); - $this->assertEquals($expected, $package->getUrl($path)); + $this->assertSame($expected, $package->getUrl($path)); } public function getConfigs() @@ -50,6 +50,6 @@ public function getConfigs() public function testGetVersion() { $package = new Package(new StaticVersionStrategy('v1')); - $this->assertEquals('v1', $package->getVersion('/foo')); + $this->assertSame('v1', $package->getVersion('/foo')); } } diff --git a/src/Symfony/Component/Asset/Tests/PackagesTest.php b/src/Symfony/Component/Asset/Tests/PackagesTest.php index b2d0de375051e..4b30e30a5e482 100644 --- a/src/Symfony/Component/Asset/Tests/PackagesTest.php +++ b/src/Symfony/Component/Asset/Tests/PackagesTest.php @@ -24,13 +24,13 @@ public function testGetterSetters() $packages->setDefaultPackage($default = $this->getMockBuilder('Symfony\Component\Asset\PackageInterface')->getMock()); $packages->addPackage('a', $a = $this->getMockBuilder('Symfony\Component\Asset\PackageInterface')->getMock()); - $this->assertEquals($default, $packages->getPackage()); - $this->assertEquals($a, $packages->getPackage('a')); + $this->assertSame($default, $packages->getPackage()); + $this->assertSame($a, $packages->getPackage('a')); $packages = new Packages($default, ['a' => $a]); - $this->assertEquals($default, $packages->getPackage()); - $this->assertEquals($a, $packages->getPackage('a')); + $this->assertSame($default, $packages->getPackage()); + $this->assertSame($a, $packages->getPackage('a')); } public function testGetVersion() @@ -40,8 +40,8 @@ public function testGetVersion() ['a' => new Package(new StaticVersionStrategy('a'))] ); - $this->assertEquals('default', $packages->getVersion('/foo')); - $this->assertEquals('a', $packages->getVersion('/foo', 'a')); + $this->assertSame('default', $packages->getVersion('/foo')); + $this->assertSame('a', $packages->getVersion('/foo', 'a')); } public function testGetUrl() @@ -51,8 +51,8 @@ public function testGetUrl() ['a' => new Package(new StaticVersionStrategy('a'))] ); - $this->assertEquals('/foo?default', $packages->getUrl('/foo')); - $this->assertEquals('/foo?a', $packages->getUrl('/foo', 'a')); + $this->assertSame('/foo?default', $packages->getUrl('/foo')); + $this->assertSame('/foo?a', $packages->getUrl('/foo', 'a')); } public function testNoDefaultPackage() diff --git a/src/Symfony/Component/Asset/Tests/PathPackageTest.php b/src/Symfony/Component/Asset/Tests/PathPackageTest.php index d00cc76c2a943..4d57559847eb1 100644 --- a/src/Symfony/Component/Asset/Tests/PathPackageTest.php +++ b/src/Symfony/Component/Asset/Tests/PathPackageTest.php @@ -23,7 +23,7 @@ class PathPackageTest extends TestCase public function testGetUrl($basePath, $format, $path, $expected) { $package = new PathPackage($basePath, new StaticVersionStrategy('v1', $format)); - $this->assertEquals($expected, $package->getUrl($path)); + $this->assertSame($expected, $package->getUrl($path)); } public function getConfigs() @@ -55,7 +55,7 @@ public function testGetUrlWithContext($basePathRequest, $basePath, $format, $pat { $package = new PathPackage($basePath, new StaticVersionStrategy('v1', $format), $this->getContext($basePathRequest)); - $this->assertEquals($expected, $package->getUrl($path)); + $this->assertSame($expected, $package->getUrl($path)); } public function getContextConfigs() @@ -83,7 +83,7 @@ public function testVersionStrategyGivesAbsoluteURL() ->willReturn('https://cdn.com/bar/main.css'); $package = new PathPackage('/subdirectory', $versionStrategy, $this->getContext('/bar')); - $this->assertEquals('https://cdn.com/bar/main.css', $package->getUrl('main.css')); + $this->assertSame('https://cdn.com/bar/main.css', $package->getUrl('main.css')); } private function getContext($basePath) diff --git a/src/Symfony/Component/Asset/Tests/UrlPackageTest.php b/src/Symfony/Component/Asset/Tests/UrlPackageTest.php index 14349b77ca305..f403a55a4af71 100644 --- a/src/Symfony/Component/Asset/Tests/UrlPackageTest.php +++ b/src/Symfony/Component/Asset/Tests/UrlPackageTest.php @@ -24,7 +24,7 @@ class UrlPackageTest extends TestCase public function testGetUrl($baseUrls, $format, $path, $expected) { $package = new UrlPackage($baseUrls, new StaticVersionStrategy('v1', $format)); - $this->assertEquals($expected, $package->getUrl($path)); + $this->assertSame($expected, $package->getUrl($path)); } public function getConfigs() @@ -65,7 +65,7 @@ public function testGetUrlWithContext($secure, $baseUrls, $format, $path, $expec { $package = new UrlPackage($baseUrls, new StaticVersionStrategy('v1', $format), $this->getContext($secure)); - $this->assertEquals($expected, $package->getUrl($path)); + $this->assertSame($expected, $package->getUrl($path)); } public function getContextConfigs() @@ -92,7 +92,7 @@ public function testVersionStrategyGivesAbsoluteURL() ->willReturn('https://cdn.com/bar/main.css'); $package = new UrlPackage('https://example.com', $versionStrategy); - $this->assertEquals('https://cdn.com/bar/main.css', $package->getUrl('main.css')); + $this->assertSame('https://cdn.com/bar/main.css', $package->getUrl('main.css')); } public function testNoBaseUrls() diff --git a/src/Symfony/Component/Asset/Tests/VersionStrategy/EmptyVersionStrategyTest.php b/src/Symfony/Component/Asset/Tests/VersionStrategy/EmptyVersionStrategyTest.php index 430146fd5070b..1728c2e99b4d4 100644 --- a/src/Symfony/Component/Asset/Tests/VersionStrategy/EmptyVersionStrategyTest.php +++ b/src/Symfony/Component/Asset/Tests/VersionStrategy/EmptyVersionStrategyTest.php @@ -29,6 +29,6 @@ public function testApplyVersion() $emptyVersionStrategy = new EmptyVersionStrategy(); $path = 'test-path'; - $this->assertEquals($path, $emptyVersionStrategy->applyVersion($path)); + $this->assertSame($path, $emptyVersionStrategy->applyVersion($path)); } } diff --git a/src/Symfony/Component/Asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.php b/src/Symfony/Component/Asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.php index d74f3f6321687..ffcc62b995d37 100644 --- a/src/Symfony/Component/Asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.php +++ b/src/Symfony/Component/Asset/Tests/VersionStrategy/JsonManifestVersionStrategyTest.php @@ -20,21 +20,21 @@ public function testGetVersion() { $strategy = $this->createStrategy('manifest-valid.json'); - $this->assertEquals('main.123abc.js', $strategy->getVersion('main.js')); + $this->assertSame('main.123abc.js', $strategy->getVersion('main.js')); } public function testApplyVersion() { $strategy = $this->createStrategy('manifest-valid.json'); - $this->assertEquals('css/styles.555def.css', $strategy->getVersion('css/styles.css')); + $this->assertSame('css/styles.555def.css', $strategy->getVersion('css/styles.css')); } public function testApplyVersionWhenKeyDoesNotExistInManifest() { $strategy = $this->createStrategy('manifest-valid.json'); - $this->assertEquals('css/other.css', $strategy->getVersion('css/other.css')); + $this->assertSame('css/other.css', $strategy->getVersion('css/other.css')); } public function testMissingManifestFileThrowsException() diff --git a/src/Symfony/Component/Asset/Tests/VersionStrategy/StaticVersionStrategyTest.php b/src/Symfony/Component/Asset/Tests/VersionStrategy/StaticVersionStrategyTest.php index c56a8726a8459..d054e842f2c55 100644 --- a/src/Symfony/Component/Asset/Tests/VersionStrategy/StaticVersionStrategyTest.php +++ b/src/Symfony/Component/Asset/Tests/VersionStrategy/StaticVersionStrategyTest.php @@ -21,7 +21,7 @@ public function testGetVersion() $version = 'v1'; $path = 'test-path'; $staticVersionStrategy = new StaticVersionStrategy($version); - $this->assertEquals($version, $staticVersionStrategy->getVersion($path)); + $this->assertSame($version, $staticVersionStrategy->getVersion($path)); } /** @@ -31,7 +31,7 @@ public function testApplyVersion($path, $version, $format) { $staticVersionStrategy = new StaticVersionStrategy($version, $format); $formatted = sprintf($format ?: '%s?%s', $path, $version); - $this->assertEquals($formatted, $staticVersionStrategy->applyVersion($path)); + $this->assertSame($formatted, $staticVersionStrategy->applyVersion($path)); } public function getConfigs() diff --git a/src/Symfony/Component/BrowserKit/HttpBrowser.php b/src/Symfony/Component/BrowserKit/HttpBrowser.php index b2331ea492a84..a1e6dd9af0119 100644 --- a/src/Symfony/Component/BrowserKit/HttpBrowser.php +++ b/src/Symfony/Component/BrowserKit/HttpBrowser.php @@ -73,18 +73,9 @@ private function getBodyAndExtraHeaders(Request $request): array } $fields = $request->getParameters(); - $hasFile = false; - foreach ($request->getFiles() as $name => $file) { - if (!isset($file['tmp_name'])) { - continue; - } - - $hasFile = true; - $fields[$name] = DataPart::fromPath($file['tmp_name'], $file['name']); - } - if ($hasFile) { - $part = new FormDataPart($fields); + if ($uploadedFiles = $this->getUploadedFiles($request->getFiles())) { + $part = new FormDataPart($uploadedFiles); return [$part->bodyToIterable(), $part->getPreparedHeaders()->toArray()]; } @@ -119,4 +110,26 @@ private function getHeaders(Request $request): array return $headers; } + + /** + * Recursively go through the list. If the file has a tmp_name, convert it to a DataPart. + * Keep the original hierarchy. + */ + private function getUploadedFiles(array $files): array + { + $uploadedFiles = []; + foreach ($files as $name => $file) { + if (!\is_array($file)) { + return $uploadedFiles; + } + if (!isset($file['tmp_name'])) { + $uploadedFiles[$name] = $this->getUploadedFiles($file); + } + if (isset($file['tmp_name'])) { + $uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']); + } + } + + return $uploadedFiles; + } } diff --git a/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php b/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php index 44eed997bdeed..fa3d531aa986d 100644 --- a/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php @@ -27,17 +27,17 @@ public function getBrowser(array $server = [], History $history = null, CookieJa /** * @dataProvider validContentTypes */ - public function testRequestHeaders(array $request, array $exepectedCall) + public function testRequestHeaders(array $requestArguments, array $expectedArguments) { $client = $this->createMock(HttpClientInterface::class); $client ->expects($this->once()) ->method('request') - ->with(...$exepectedCall) + ->with(...$expectedArguments) ->willReturn($this->createMock(ResponseInterface::class)); $browser = new HttpBrowser($client); - $browser->request(...$request); + $browser->request(...$requestArguments); } public function validContentTypes() @@ -61,7 +61,7 @@ public function validContentTypes() ]; } - public function testMultiPartRequest() + public function testMultiPartRequestWithSingleFile() { $client = $this->createMock(HttpClientInterface::class); $client @@ -81,4 +81,90 @@ public function testMultiPartRequest() file_put_contents($path, 'my_file'); $browser->request('POST', 'http://example.com/', [], ['file' => ['tmp_name' => $path, 'name' => 'foo']]); } + + public function testMultiPartRequestWithNormalFlatArray() + { + $client = $this->createMock(HttpClientInterface::class); + $this->expectClientToSendRequestWithFiles($client, ['file1_content', 'file2_content']); + + $browser = new HttpBrowser($client); + $browser->request('POST', 'http://example.com/', [], [ + 'file1' => $this->getUploadedFile('file1'), + 'file2' => $this->getUploadedFile('file2'), + ]); + } + + public function testMultiPartRequestWithNormalNestedArray() + { + $client = $this->createMock(HttpClientInterface::class); + $this->expectClientToSendRequestWithFiles($client, ['file1_content', 'file2_content']); + + $browser = new HttpBrowser($client); + $browser->request('POST', 'http://example.com/', [], [ + 'level1' => [ + 'level2' => [ + 'file1' => $this->getUploadedFile('file1'), + 'file2' => $this->getUploadedFile('file2'), + ], + ], + ]); + } + + public function testMultiPartRequestWithBracketedArray() + { + $client = $this->createMock(HttpClientInterface::class); + $this->expectClientToSendRequestWithFiles($client, ['file1_content', 'file2_content']); + + $browser = new HttpBrowser($client); + $browser->request('POST', 'http://example.com/', [], [ + 'form[file1]' => $this->getUploadedFile('file1'), + 'form[file2]' => $this->getUploadedFile('file2'), + ]); + } + + public function testMultiPartRequestWithInvalidItem() + { + $client = $this->createMock(HttpClientInterface::class); + $this->expectClientToSendRequestWithFiles($client, ['file1_content']); + + $browser = new HttpBrowser($client); + $browser->request('POST', 'http://example.com/', [], [ + 'file1' => $this->getUploadedFile('file1'), + 'file2' => 'INVALID', + ]); + } + + private function uploadFile(string $data): string + { + $path = tempnam(sys_get_temp_dir(), 'http'); + file_put_contents($path, $data); + + return $path; + } + + private function getUploadedFile(string $name): array + { + return [ + 'tmp_name' => $this->uploadFile($name.'_content'), + 'name' => $name.'_name', + ]; + } + + protected function expectClientToSendRequestWithFiles(HttpClientInterface $client, $fileContents) + { + $client + ->expects($this->once()) + ->method('request') + ->with('POST', 'http://example.com/', $this->callback(function ($options) use ($fileContents) { + $this->assertStringContainsString('Content-Type: multipart/form-data', implode('', $options['headers'])); + $this->assertInstanceOf('\Generator', $options['body']); + $body = implode('', iterator_to_array($options['body'], false)); + foreach ($fileContents as $content) { + $this->assertStringContainsString($content, $body); + } + + return true; + })) + ->willReturn($this->createMock(ResponseInterface::class)); + } } diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php index a7b6780acd457..99a671516af6f 100644 --- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php @@ -427,7 +427,7 @@ private function getConnection(): object } else { switch ($this->driver = $this->conn->getDriver()->getName()) { case 'mysqli': - throw new \LogicException(sprintf('The adapter "%s" does not support the mysqli driver, use pdo_mysql instead.', \get_class($this))); + throw new \LogicException(sprintf('The adapter "%s" does not support the mysqli driver, use pdo_mysql instead.', static::class)); case 'pdo_mysql': case 'drizzle_pdo_mysql': $this->driver = 'mysql'; diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php index acdfa883f579d..f382f223f2635 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php @@ -80,7 +80,7 @@ public function __construct($redisClient, string $namespace = '', int $defaultLi foreach (\is_array($compression) ? $compression : [$compression] as $c) { if (\Redis::COMPRESSION_NONE !== $c) { - throw new InvalidArgumentException(sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', \get_class($this), DeflateMarshaller::class)); + throw new InvalidArgumentException(sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); } } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php index 10188607d1cb8..a4a7fc1b58ca2 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php @@ -55,7 +55,7 @@ public function testUnserializable() public function testVersion() { - $namespace = str_replace('\\', '.', \get_class($this)); + $namespace = str_replace('\\', '.', static::class); $pool1 = new ApcuAdapter($namespace, 0, 'p1'); @@ -80,7 +80,7 @@ public function testVersion() public function testNamespace() { - $namespace = str_replace('\\', '.', \get_class($this)); + $namespace = str_replace('\\', '.', static::class); $pool1 = new ApcuAdapter($namespace.'_1', 0, 'p1'); diff --git a/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php b/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php index d166c5322a659..f9d73c30f53ee 100644 --- a/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php +++ b/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php @@ -122,6 +122,12 @@ public function clear(string $prefix = '') { $this->deferred = []; if ($cleared = $this->versioningIsEnabled) { + if ('' === $namespaceVersionToClear = $this->namespaceVersion) { + foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + $namespaceVersionToClear = $v; + } + } + $namespaceToClear = $this->namespace.$namespaceVersionToClear; $namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::NS_SEPARATOR, 5); try { $cleared = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0); @@ -132,10 +138,12 @@ public function clear(string $prefix = '') $this->namespaceVersion = $namespaceVersion; $this->ids = []; } + } else { + $namespaceToClear = $this->namespace.$prefix; } try { - return $this->doClear($this->namespace.$prefix) || $cleared; + return $this->doClear($namespaceToClear) || $cleared; } catch (\Exception $e) { CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e]); diff --git a/src/Symfony/Component/Cache/Traits/ContractsTrait.php b/src/Symfony/Component/Cache/Traits/ContractsTrait.php index c5827c3b73bc9..06070c970cac5 100644 --- a/src/Symfony/Component/Cache/Traits/ContractsTrait.php +++ b/src/Symfony/Component/Cache/Traits/ContractsTrait.php @@ -52,7 +52,7 @@ public function setCallbackWrapper(?callable $callbackWrapper): callable private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null) { if (0 > $beta = $beta ?? 1.0) { - throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', \get_class($this), $beta)); + throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)); } static $setMetadata; diff --git a/src/Symfony/Component/Config/Definition/BaseNode.php b/src/Symfony/Component/Config/Definition/BaseNode.php index d72162012c88c..72325d8480a14 100644 --- a/src/Symfony/Component/Config/Definition/BaseNode.php +++ b/src/Symfony/Component/Config/Definition/BaseNode.php @@ -508,7 +508,7 @@ private static function resolvePlaceholderValue($value) private function doValidateType($value): void { if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) { - $e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', \get_class($this), $this->getPath())); + $e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', static::class, $this->getPath())); $e->setPath($this->getPath()); throw $e; diff --git a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php index 32f32e0a6b424..b7659a364109d 100644 --- a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php +++ b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php @@ -91,7 +91,7 @@ private function writeNode(NodeInterface $node, int $depth = 0, bool $root = fal } if ($prototype instanceof PrototypedArrayNode) { - $prototype->setName($key); + $prototype->setName($key ?? ''); $children = [$key => $prototype]; } elseif ($prototype instanceof ArrayNode) { $children = $prototype->getChildren(); diff --git a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php index a547fb461236f..cce387ccfe3c2 100644 --- a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php +++ b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php @@ -94,6 +94,8 @@ public function isFresh(int $timestamp): bool } } catch (\Throwable $e) { $exists[1] = $e->getMessage(); + + throw $e; } finally { self::$autoloadedClass = $autoloadedClass; if (!--self::$autoloadLevel) { diff --git a/src/Symfony/Component/Config/Tests/Fixtures/ParseError.php b/src/Symfony/Component/Config/Tests/Fixtures/ParseError.php new file mode 100644 index 0000000000000..6bb2213824830 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Fixtures/ParseError.php @@ -0,0 +1,7 @@ +assertFalse($res->isFresh(0)); } + + public function testParseError() + { + $this->expectException('ParseError'); + + $res = new ClassExistenceResource(ParseError::class, false); + $res->isFresh(0); + } } diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index dbbdabd5d9f6f..c1fd41b80b9b1 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -36,7 +36,6 @@ use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\StreamableInputInterface; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -858,18 +857,6 @@ protected function configureIO(InputInterface $input, OutputInterface $output) if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) { $input->setInteractive(false); - } else { - $inputStream = null; - - if ($input instanceof StreamableInputInterface) { - $inputStream = $input->getStream(); - } - - $inputStream = !$inputStream && \defined('STDIN') ? STDIN : $inputStream; - - if ((!$inputStream || !stream_isatty($inputStream)) && false === getenv('SHELL_INTERACTIVE')) { - $input->setInteractive(false); - } } switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) { diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index b7bf16cfba2f3..07da4a9223c70 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -55,7 +55,7 @@ class Command */ public static function getDefaultName() { - $class = \get_called_class(); + $class = static::class; $r = new \ReflectionProperty($class, 'defaultName'); return $class === $r->class ? static::$defaultName : null; @@ -255,7 +255,7 @@ public function run(InputInterface $input, OutputInterface $output) $statusCode = $this->execute($input, $output); if (!\is_int($statusCode)) { - throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, %s returned.', \get_class($this), \gettype($statusCode))); + throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, %s returned.', static::class, \gettype($statusCode))); } } @@ -344,7 +344,7 @@ public function setDefinition($definition) public function getDefinition() { if (null === $this->definition) { - throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', \get_class($this))); + throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); } return $this->definition; diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php index 0a641103d8291..5aefa3d7b9e02 100644 --- a/src/Symfony/Component/Console/Helper/Table.php +++ b/src/Symfony/Component/Console/Helper/Table.php @@ -589,7 +589,9 @@ private function calculateRowCount(): int ++$numberOfRows; // Add row for header separator } - ++$numberOfRows; // Add row for footer separator + if (\count($this->rows) > 0) { + ++$numberOfRows; // Add row for footer separator + } return $numberOfRows; } diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php index eea624e05da11..271752f1444e6 100644 --- a/src/Symfony/Component/Console/Style/SymfonyStyle.php +++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php @@ -290,7 +290,7 @@ public function choice(string $question, array $choices, $default = null) { if (null !== $default) { $values = array_flip($choices); - $default = $values[$default]; + $default = isset($values[$default]) ? $values[$default] : $default; } return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); diff --git a/src/Symfony/Component/Console/Tester/ApplicationTester.php b/src/Symfony/Component/Console/Tester/ApplicationTester.php index 9da40cf375c4c..d021c14358f2d 100644 --- a/src/Symfony/Component/Console/Tester/ApplicationTester.php +++ b/src/Symfony/Component/Console/Tester/ApplicationTester.php @@ -56,19 +56,12 @@ public function run(array $input, array $options = []) $this->input->setInteractive($options['interactive']); } - $shellInteractive = getenv('SHELL_INTERACTIVE'); - if ($this->inputs) { $this->input->setStream(self::createStream($this->inputs)); - putenv('SHELL_INTERACTIVE=1'); } $this->initOutput($options); - $this->statusCode = $this->application->run($this->input, $this->output); - - putenv($shellInteractive ? "SHELL_INTERACTIVE=$shellInteractive" : 'SHELL_INTERACTIVE'); - - return $this->statusCode; + return $this->statusCode = $this->application->run($this->input, $this->output); } } diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 0081504b77de1..cd8904ea72203 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -883,8 +883,7 @@ public function testRenderAnonymousException() $application = new Application(); $application->setAutoExit(false); $application->register('foo')->setCode(function () { - throw new class('') extends \InvalidArgumentException { - }; + throw new class('') extends \InvalidArgumentException { }; }); $tester = new ApplicationTester($application); @@ -894,8 +893,7 @@ public function testRenderAnonymousException() $application = new Application(); $application->setAutoExit(false); $application->register('foo')->setCode(function () { - throw new \InvalidArgumentException(sprintf('Dummy type "%s" is invalid.', \get_class(new class() { - }))); + throw new \InvalidArgumentException(sprintf('Dummy type "%s" is invalid.', \get_class(new class() { }))); }); $tester = new ApplicationTester($application); @@ -908,8 +906,7 @@ public function testRenderExceptionStackTraceContainsRootException() $application = new Application(); $application->setAutoExit(false); $application->register('foo')->setCode(function () { - throw new class('') extends \InvalidArgumentException { - }; + throw new class('') extends \InvalidArgumentException { }; }); $tester = new ApplicationTester($application); @@ -919,8 +916,7 @@ public function testRenderExceptionStackTraceContainsRootException() $application = new Application(); $application->setAutoExit(false); $application->register('foo')->setCode(function () { - throw new \InvalidArgumentException(sprintf('Dummy type "%s" is invalid.', \get_class(new class() { - }))); + throw new \InvalidArgumentException(sprintf('Dummy type "%s" is invalid.', \get_class(new class() { }))); }); $tester = new ApplicationTester($application); @@ -1696,6 +1692,31 @@ public function testAllExcludesDisabledLazyCommand() $this->assertArrayNotHasKey('disabled', $application->all()); } + public function testFindAlternativesDoesNotLoadSameNamespaceCommandsOnExactMatch() + { + $application = new Application(); + $application->setAutoExit(false); + + $loaded = []; + + $application->setCommandLoader(new FactoryCommandLoader([ + 'foo:bar' => function () use (&$loaded) { + $loaded['foo:bar'] = true; + + return (new Command('foo:bar'))->setCode(function () {}); + }, + 'foo' => function () use (&$loaded) { + $loaded['foo'] = true; + + return (new Command('foo'))->setCode(function () {}); + }, + ])); + + $application->run(new ArrayInput(['command' => 'foo']), new NullOutput()); + + $this->assertSame(['foo' => true], $loaded); + } + protected function getDispatcher($skipCommand = false) { $dispatcher = new EventDispatcher(); diff --git a/src/Symfony/Component/Console/Tests/Helper/TableTest.php b/src/Symfony/Component/Console/Tests/Helper/TableTest.php index 124309dd5b02c..c4acc3fbff9c3 100644 --- a/src/Symfony/Component/Console/Tests/Helper/TableTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/TableTest.php @@ -951,6 +951,38 @@ public function testAppendRowWithoutSectionOutput() $table->appendRow(['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25']); } + public function testSectionOutputHandlesZeroRowsAfterRender() + { + $sections = []; + $stream = $this->getOutputStream(true); + $output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter()); + $output->writeln('My Table'); + $table = new Table($output); + $table + ->setHeaders(['ISBN', 'Title', 'Author', 'Price']) + ->setRows([]); + + $table->render(); + + $table->appendRow(['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25']); + + $expected = + <<assertEquals($expected, $this->getOutputContent($output)); + } + public function testIsNotDefinedStyleException() { $this->expectException('Symfony\Component\Console\Exception\InvalidArgumentException'); diff --git a/src/Symfony/Component/Console/Tests/phpt/uses_stdin_as_interactive_input.phpt b/src/Symfony/Component/Console/Tests/phpt/uses_stdin_as_interactive_input.phpt new file mode 100644 index 0000000000000..db1bb4ce436e2 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/phpt/uses_stdin_as_interactive_input.phpt @@ -0,0 +1,28 @@ +--STDIN-- +Hello World +--FILE-- +register('app') + ->setCode(function(InputInterface $input, OutputInterface $output) { + $output->writeln((new QuestionHelper())->ask($input, $output, new Question('Foo?'))); + }) + ->getApplication() + ->setDefaultCommand('app', true) + ->run() +; +--EXPECT-- +Foo?Hello World diff --git a/src/Symfony/Component/CssSelector/Node/AbstractNode.php b/src/Symfony/Component/CssSelector/Node/AbstractNode.php index 9c5cbddc0214f..1306aeacb95e3 100644 --- a/src/Symfony/Component/CssSelector/Node/AbstractNode.php +++ b/src/Symfony/Component/CssSelector/Node/AbstractNode.php @@ -31,7 +31,7 @@ abstract class AbstractNode implements NodeInterface public function getNodeName(): string { if (null === $this->nodeName) { - $this->nodeName = preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', \get_called_class()); + $this->nodeName = preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', static::class); } return $this->nodeName; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php index 2920af1047ca5..0d00d52dca3f1 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -209,7 +209,7 @@ private function getExpressionLanguage(): ExpressionLanguage $arg = $this->processValue(new Reference($id)); $this->inExpression = false; if (!$arg instanceof Reference) { - throw new RuntimeException(sprintf('"%s::processValue()" must return a Reference when processing an expression, %s returned for service("%s").', \get_class($this), \is_object($arg) ? \get_class($arg) : \gettype($arg), $id)); + throw new RuntimeException(sprintf('"%s::processValue()" must return a Reference when processing an expression, %s returned for service("%s").', static::class, \is_object($arg) ? \get_class($arg) : \gettype($arg), $id)); } $arg = sprintf('"%s"', $arg); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php index 9b3760b49758d..1a2c829854dc6 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php @@ -54,78 +54,81 @@ private function findAndSortTaggedServices($tagName, ContainerBuilder $container foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) { $class = $r = null; - $priority = 0; - if (isset($attributes[0]['priority'])) { - $priority = $attributes[0]['priority']; - } elseif ($defaultPriorityMethod) { - $class = $container->getDefinition($serviceId)->getClass(); - $class = $container->getParameterBag()->resolveValue($class) ?: null; - - if (($r = $container->getReflectionClass($class)) && $r->hasMethod($defaultPriorityMethod)) { - if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); - } - - if (!$rm->isPublic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); - } - - $priority = $rm->invoke(null); - - if (!\is_int($priority)) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer, got %s: tag "%s" on service "%s".', $class, $defaultPriorityMethod, \gettype($priority), $tagName, $serviceId)); - } - } - } - - if (null === $indexAttribute && !$needsIndexes) { - $services[$priority][] = new Reference($serviceId); - - continue; - } - if (!$class) { - $class = $container->getDefinition($serviceId)->getClass(); - $class = $container->getParameterBag()->resolveValue($class) ?: null; - } + $defaultPriority = null; + $defaultIndex = null; - if (null !== $indexAttribute && isset($attributes[0][$indexAttribute])) { - $services[$priority][$attributes[0][$indexAttribute]] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $attributes[0][$indexAttribute]); + foreach ($attributes as $attribute) { + $index = $priority = null; - continue; - } + if (isset($attribute['priority'])) { + $priority = $attribute['priority']; + } elseif (null === $defaultPriority && $defaultPriorityMethod) { + $class = $container->getDefinition($serviceId)->getClass(); + $class = $container->getParameterBag()->resolveValue($class) ?: null; - if (!$r && !$r = $container->getReflectionClass($class)) { - throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $serviceId)); - } + if (($r = ($r ?? $container->getReflectionClass($class))) && $r->hasMethod($defaultPriorityMethod)) { + if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); + } - $class = $r->name; + if (!$rm->isPublic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); + } - if (!$r->hasMethod($defaultIndexMethod)) { - if ($needsIndexes) { - $services[$priority][$serviceId] = new TypedReference($serviceId, $class); + $defaultPriority = $rm->invoke(null); - continue; + if (!\is_int($defaultPriority)) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer, got %s: tag "%s" on service "%s".', $class, $defaultPriorityMethod, \gettype($priority), $tagName, $serviceId)); + } + } } - throw new InvalidArgumentException(sprintf('Method "%s::%s()" not found: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); - } + $priority = $priority ?? $defaultPriority ?? 0; + + if (null !== $indexAttribute && isset($attribute[$indexAttribute])) { + $index = $attribute[$indexAttribute]; + } elseif (null === $defaultIndex && null === $indexAttribute && !$needsIndexes) { + // With partially associative array, insertion to get next key is simpler. + $services[$priority][] = null; + end($services[$priority]); + $defaultIndex = key($services[$priority]); + } elseif (null === $defaultIndex && $defaultIndexMethod) { + $class = $container->getDefinition($serviceId)->getClass(); + $class = $container->getParameterBag()->resolveValue($class) ?: null; + + if (($r = ($r ?? $container->getReflectionClass($class))) && $r->hasMethod($defaultIndexMethod)) { + if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); + } + + if (!$rm->isPublic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); + } + + $defaultIndex = $rm->invoke(null); + + if (!\is_string($defaultIndex)) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return a string, got %s: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, \gettype($defaultIndex), $tagName, $serviceId, $indexAttribute)); + } + } - if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); - } + $defaultIndex = $defaultIndex ?? $serviceId; + } - if (!$rm->isPublic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); - } + $index = $index ?? $defaultIndex; - $key = $rm->invoke(null); + $reference = null; + if (!$class || 'stdClass' === $class) { + $reference = new Reference($serviceId); + } elseif ($index === $serviceId) { + $reference = new TypedReference($serviceId, $class); + } else { + $reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, \is_string($index) ? $index : null); + } - if (!\is_string($key)) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return a string, got %s: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, \gettype($key), $tagName, $serviceId, $indexAttribute)); + $services[$priority][$index] = $reference; } - - $services[$priority][$key] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $key); } if ($services) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php index c21dd91c5159e..09836cd302d1f 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php @@ -32,10 +32,11 @@ class GraphvizDumper extends Dumper { private $nodes; private $edges; + // All values should be strings private $options = [ 'graph' => ['ratio' => 'compress'], - 'node' => ['fontsize' => 11, 'fontname' => 'Arial', 'shape' => 'record'], - 'edge' => ['fontsize' => 9, 'fontname' => 'Arial', 'color' => 'grey', 'arrowhead' => 'open', 'arrowsize' => 0.5], + 'node' => ['fontsize' => '11', 'fontname' => 'Arial', 'shape' => 'record'], + 'edge' => ['fontsize' => '9', 'fontname' => 'Arial', 'color' => 'grey', 'arrowhead' => 'open', 'arrowsize' => '0.5'], 'node.instance' => ['fillcolor' => '#9999ff', 'style' => 'filled'], 'node.definition' => ['fillcolor' => '#eeeeee'], 'node.missing' => ['fillcolor' => '#ff9999', 'style' => 'filled'], diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index bd25e3f0ec21d..987605f98f25c 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -126,9 +126,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv) } if (false !== $i || 'string' !== $prefix) { - if (null === $env = $getEnv($name)) { - return null; - } + $env = $getEnv($name); } elseif (isset($_ENV[$name])) { $env = $_ENV[$name]; } elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) { @@ -173,10 +171,16 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv) throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name)); } - if (null === $env = $this->container->getParameter("env($name)")) { - return null; - } + $env = $this->container->getParameter("env($name)"); + } + } + + if (null === $env) { + if (!isset($this->getProvidedTypes()[$prefix])) { + throw new RuntimeException(sprintf('Unsupported env var prefix "%s".', $prefix)); } + + return null; } if (!is_scalar($env)) { diff --git a/src/Symfony/Component/DependencyInjection/Extension/Extension.php b/src/Symfony/Component/DependencyInjection/Extension/Extension.php index ffa47074d6944..81070c911fab9 100644 --- a/src/Symfony/Component/DependencyInjection/Extension/Extension.php +++ b/src/Symfony/Component/DependencyInjection/Extension/Extension.php @@ -66,7 +66,7 @@ public function getNamespace() */ public function getAlias() { - $className = \get_class($this); + $className = static::class; if ('Extension' != substr($className, -9)) { throw new BadMethodCallException('This extension does not follow the naming convention; you must overwrite the getAlias() method.'); } @@ -80,7 +80,7 @@ public function getAlias() */ public function getConfiguration(array $config, ContainerBuilder $container) { - $class = \get_class($this); + $class = static::class; if (false !== strpos($class, "\0")) { return null; // ignore anonymous classes diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractConfigurator.php index 539eb3914d1e1..2a9121a97bca9 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractConfigurator.php @@ -31,7 +31,7 @@ public function __call(string $method, array $args) return $this->{'set'.$method}(...$args); } - throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', \get_class($this), $method)); + throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', static::class, $method)); } /** diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index 26577c9ef881a..09290f9b68d52 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -57,7 +57,7 @@ public function import($resource, $type = null, $ignoreErrors = false, $sourceRe if ($ignoreNotFound = 'not_found' === $ignoreErrors) { $args[2] = false; } elseif (!\is_bool($ignoreErrors)) { - throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to %s::import(): boolean or "not_found" expected, %s given.', \get_class($this), \gettype($ignoreErrors))); + throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to %s::import(): boolean or "not_found" expected, %s given.', static::class, \gettype($ignoreErrors))); } try { @@ -159,7 +159,7 @@ private function findClasses(string $namespace, string $pattern, array $excludeP $excludePrefix = null; $excludePatterns = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns)); foreach ($excludePatterns as $excludePattern) { - foreach ($this->glob($excludePattern, true, $resource, false, true) as $path => $info) { + foreach ($this->glob($excludePattern, true, $resource, true, true) as $path => $info) { if (null === $excludePrefix) { $excludePrefix = $resource->getPrefix(); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php index 8ed1e4277870a..e6384803c36ea 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php @@ -49,7 +49,7 @@ public function load($resource, string $type = null) /** * {@inheritdoc} */ - public function supports($resource, string $type = null) + public function supports($resource, string $type = null) { if (!\is_string($resource)) { return false; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index 7d4f01a2657f3..f6c255484adc6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -314,6 +314,32 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethod() $this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param); } + public function testTaggedIteratorWithMultipleIndexAttribute() + { + $container = new ContainerBuilder(); + $container->register(BarTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'bar']) + ->addTag('foo_bar', ['foo' => 'bar_duplicate']) + ; + $container->register(FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar') + ->addTag('foo_bar') + ; + $container->register(FooBarTaggedClass::class) + ->addArgument(new TaggedIteratorArgument('foo_bar', 'foo')) + ->setPublic(true) + ; + + $container->compile(); + + $s = $container->get(FooBarTaggedClass::class); + + $param = iterator_to_array($s->getParam()->getIterator()); + $this->assertSame(['bar' => $container->get(BarTagClass::class), 'bar_duplicate' => $container->get(BarTagClass::class), 'foo_tag_class' => $container->get(FooTagClass::class)], $param); + } + public function testTaggedServiceWithDefaultPriorityMethod() { $container = new ContainerBuilder(); @@ -350,7 +376,7 @@ public function testTaggedServiceLocatorWithIndexAttribute() ->addTag('foo_bar') ; $container->register('foo_bar_tagged', FooBarTaggedClass::class) - ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo'))) + ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', null, true))) ->setPublic(true) ; @@ -369,6 +395,40 @@ public function testTaggedServiceLocatorWithIndexAttribute() $this->assertSame(['bar' => $container->get('bar_tag'), 'foo_tag_class' => $container->get('foo_tag')], $same); } + public function testTaggedServiceLocatorWithMultipleIndexAttribute() + { + $container = new ContainerBuilder(); + $container->register('bar_tag', BarTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'bar']) + ->addTag('foo_bar', ['foo' => 'bar_duplicate']) + ; + $container->register('foo_tag', FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar') + ->addTag('foo_bar') + ; + $container->register('foo_bar_tagged', FooBarTaggedClass::class) + ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', null, true))) + ->setPublic(true) + ; + + $container->compile(); + + $s = $container->get('foo_bar_tagged'); + + /** @var ServiceLocator $serviceLocator */ + $serviceLocator = $s->getParam(); + $this->assertTrue($s->getParam() instanceof ServiceLocator, sprintf('Wrong instance, should be an instance of ServiceLocator, %s given', \is_object($serviceLocator) ? \get_class($serviceLocator) : \gettype($serviceLocator))); + + $same = [ + 'bar' => $serviceLocator->get('bar'), + 'bar_duplicate' => $serviceLocator->get('bar_duplicate'), + 'foo_tag_class' => $serviceLocator->get('foo_tag_class'), + ]; + $this->assertSame(['bar' => $container->get('bar_tag'), 'bar_duplicate' => $container->get('bar_tag'), 'foo_tag_class' => $container->get('foo_tag')], $same); + } + public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod() { $container = new ContainerBuilder(); @@ -381,7 +441,7 @@ public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod() ->addTag('foo_bar', ['foo' => 'foo']) ; $container->register('foo_bar_tagged', FooBarTaggedClass::class) - ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar'))) + ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar', true))) ->setPublic(true) ; diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index e8a63a24782fb..5a3cd6c7e4d0f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1097,7 +1097,7 @@ public function testThrowsExceptionWhenSetServiceOnACompiledContainer() $container->set('a', new \stdClass()); } - public function testThrowsExceptionWhenAddServiceOnACompiledContainer() + public function testNoExceptionWhenAddServiceOnACompiledContainer() { $container = new ContainerBuilder(); $container->compile(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php index df329b4f7e316..313bce597366f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php @@ -9,6 +9,7 @@ use Symfony\Component\DependencyInjection\EnvVarLoaderInterface; use Symfony\Component\DependencyInjection\EnvVarProcessor; use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; class EnvVarProcessorTest extends TestCase { @@ -595,4 +596,17 @@ public function loadEnvVars(): array $this->assertSame(2, $index); } + + public function testGetEnvInvalidPrefixWithDefault() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Unsupported env var prefix'); + $processor = new EnvVarProcessor(new Container()); + + $processor->getEnv('unknown', 'default::FAKE', function ($name) { + $this->assertSame('default::FAKE', $name); + + return null; + }); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index 0302981f0f9e2..9c368e23aba5c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -136,6 +136,13 @@ public function testRegisterClassesWithExclude() ], array_keys($container->getAliases()) ); + + $loader->registerClasses( + new Definition(), + 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', + 'Prototype/*', + 'Prototype/NotExistingDir' + ); } public function testRegisterClassesWithExcludeAsArray() diff --git a/src/Symfony/Component/DomCrawler/Form.php b/src/Symfony/Component/DomCrawler/Form.php index 8205923b76f13..56d10108c1a98 100644 --- a/src/Symfony/Component/DomCrawler/Form.php +++ b/src/Symfony/Component/DomCrawler/Form.php @@ -89,10 +89,6 @@ public function getValues() { $values = []; foreach ($this->fields->all() as $name => $field) { - if ($field->isDisabled()) { - continue; - } - if (!$field instanceof Field\FileFormField && $field->hasValue()) { $values[$name] = $field->getValue(); } @@ -115,10 +111,6 @@ public function getFiles() $files = []; foreach ($this->fields->all() as $name => $field) { - if ($field->isDisabled()) { - continue; - } - if ($field instanceof Field\FileFormField) { $files[$name] = $field->getValue(); } @@ -281,7 +273,7 @@ public function remove(string $name) /** * Gets a named field. * - * @return FormField The field instance + * @return FormField|FormField[]|FormField[][] The value of the field * * @throws \InvalidArgumentException When field is not present in this form */ @@ -325,7 +317,7 @@ public function offsetExists($name) * * @param string $name The field name * - * @return FormField The associated Field instance + * @return FormField|FormField[]|FormField[][] The value of the field * * @throws \InvalidArgumentException if the field does not exist */ @@ -467,7 +459,7 @@ private function initialize() private function addField(\DOMElement $node) { - if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { + if (!$node->hasAttribute('name') || !$node->getAttribute('name') || $node->hasAttribute('disabled')) { return; } diff --git a/src/Symfony/Component/DomCrawler/FormFieldRegistry.php b/src/Symfony/Component/DomCrawler/FormFieldRegistry.php index f8764119a2a0f..7cfdd53494915 100644 --- a/src/Symfony/Component/DomCrawler/FormFieldRegistry.php +++ b/src/Symfony/Component/DomCrawler/FormFieldRegistry.php @@ -66,7 +66,7 @@ public function remove(string $name) /** * Returns the value of the field based on the fully qualifed name and its children. * - * @return mixed The value of the field + * @return FormField|FormField[]|FormField[][] The value of the field * * @throws \InvalidArgumentException if the field does not exist */ diff --git a/src/Symfony/Component/DomCrawler/Tests/FormTest.php b/src/Symfony/Component/DomCrawler/Tests/FormTest.php index e02c8f911373b..52e778bed9b4a 100644 --- a/src/Symfony/Component/DomCrawler/Tests/FormTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/FormTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DomCrawler\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\DomCrawler\Field\TextareaFormField; use Symfony\Component\DomCrawler\Form; use Symfony\Component\DomCrawler\FormFieldRegistry; @@ -158,12 +159,12 @@ public function testConstructorHandlesFormValues() public function testMultiValuedFields() { $form = $this->createForm(' - - - - - - + + + + + + '); @@ -226,10 +227,10 @@ public function provideInitializeValues() [], ], [ - 'takes into account disabled input fields', + 'skips disabled input fields', '', - ['foo' => ['InputFormField', 'foo']], + [], ], [ 'appends the submitted button value', @@ -965,7 +966,7 @@ protected function createTestMultipleForm() return $dom; } - public function testgetPhpValuesWithEmptyTextarea() + public function testGetPhpValuesWithEmptyTextarea() { $dom = new \DOMDocument(); $dom->loadHTML(' @@ -980,4 +981,34 @@ public function testgetPhpValuesWithEmptyTextarea() $form = new Form($nodes->item(0), 'http://example.com'); $this->assertEquals($form->getPhpValues(), ['example' => '']); } + + public function testGetReturnTypes() + { + $dom = new \DOMDocument(); + $dom->loadHTML(' + + + + + ' + ); + + $nodes = $dom->getElementsByTagName('form'); + $form = new Form($nodes->item(0), 'http://example.com'); + + // FormField + $this->assertInstanceOf(TextareaFormField::class, $textareaFormField = $form->get('foo[collection][0][bar]')); + + // Array of FormField + $this->assertSame([ + 'bar' => $textareaFormField, + ], $form->get('foo[collection][0]')); + + // Array of array of FormField + $this->assertSame([ + [ + 'bar' => $textareaFormField, + ], + ], $form->get('foo[collection]')); + } } diff --git a/src/Symfony/Component/Dotenv/README.md b/src/Symfony/Component/Dotenv/README.md index c21223b04e42e..10bfff14ba78d 100644 --- a/src/Symfony/Component/Dotenv/README.md +++ b/src/Symfony/Component/Dotenv/README.md @@ -2,7 +2,7 @@ Dotenv Component ================ Symfony Dotenv parses `.env` files to make environment variables stored in them -accessible via `$_SERVER`, `$_ENV` and optionally `getenv()`. +accessible via `$_SERVER` or `$_ENV`. Resources --------- diff --git a/src/Symfony/Component/ErrorHandler/Debug.php b/src/Symfony/Component/ErrorHandler/Debug.php index 50d1789f09243..9cc8ec17a8270 100644 --- a/src/Symfony/Component/ErrorHandler/Debug.php +++ b/src/Symfony/Component/ErrorHandler/Debug.php @@ -29,6 +29,11 @@ public static function enable(): ErrorHandler ini_set('display_errors', 1); } + @ini_set('zend.assertions', 1); + ini_set('assert.active', 1); + ini_set('assert.warning', 0); + ini_set('assert.exception', 1); + DebugClassLoader::enable(); return ErrorHandler::register(new ErrorHandler(new BufferingLogger(), true)); diff --git a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php index a9ce96efda5e6..e210acdf107c4 100644 --- a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php +++ b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php @@ -427,17 +427,17 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array } } - if ($refl->isInterface() && false !== strpos($doc, 'method') && preg_match_all('#\n \* @method\s+(static\s+)?+(?:[\w\|&\[\]\\\]+\s+)?(\w+(?:\s*\([^\)]*\))?)+(.+?([[:punct:]]\s*)?)?(?=\r?\n \*(?: @|/$|\r?\n))#', $doc, $notice, PREG_SET_ORDER)) { + if ($refl->isInterface() && false !== strpos($doc, 'method') && preg_match_all('#\n \* @method\s+(static\s+)?+([\w\|&\[\]\\\]+\s+)?(\w+(?:\s*\([^\)]*\))?)+(.+?([[:punct:]]\s*)?)?(?=\r?\n \*(?: @|/$|\r?\n))#', $doc, $notice, PREG_SET_ORDER)) { foreach ($notice as $method) { - $static = '' !== $method[1]; - $name = $method[2]; - $description = $method[3] ?? null; + $static = '' !== $method[1] && !empty($method[2]); + $name = $method[3]; + $description = $method[4] ?? null; if (false === strpos($name, '(')) { $name .= '()'; } if (null !== $description) { $description = trim($description); - if (!isset($method[4])) { + if (!isset($method[5])) { $description .= '.'; } } @@ -606,7 +606,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array if ($canAddReturnType && 'docblock' === $this->patchTypes['force'] && false === strpos($method->getFileName(), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) { $this->patchMethod($method, $returnType, $declaringFile, $normalizedType); } elseif ('' !== $declaringClass && $this->patchTypes['deprecations']) { - $deprecations[] = sprintf('Method "%s::%s()" will return "%s" as of its next major version. Doing the same in child class "%s" will be required when upgrading.', $declaringClass, $method->name, $normalizedType, $className); + $deprecations[] = sprintf('Method "%s::%s()" will return "%s" as of its next major version. Doing the same in %s "%s" will be required when upgrading.', $declaringClass, $method->name, $normalizedType, interface_exists($declaringClass) ? 'implementation' : 'child class', $className); } } } @@ -663,7 +663,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array foreach ($matches as list(, $parameterType, $parameterName)) { if (!isset($definedParameters[$parameterName])) { $parameterType = trim($parameterType); - self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its parent class "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, $className); + self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its %s "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, interface_exists($className) ? 'interface' : 'parent class', $className); } } } diff --git a/src/Symfony/Component/ErrorHandler/ErrorHandler.php b/src/Symfony/Component/ErrorHandler/ErrorHandler.php index b3afb53c9efb0..2ac16e380d032 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorHandler.php +++ b/src/Symfony/Component/ErrorHandler/ErrorHandler.php @@ -413,6 +413,11 @@ public function handleError(int $type, string $message, string $file, int $line) $throw = $this->thrownErrors & $type & $level; $type &= $level | $this->screamedErrors; + // Never throw on warnings triggered by assert() + if (E_WARNING === $type && 'a' === $message[0] && 0 === strncmp($message, 'assert(): ', 10)) { + $throw = 0; + } + if (!$type || (!$log && !$throw)) { return !$silenced && $type && $log; } 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 e178fe0fcd413..a7090fbe8909e 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/views/traces_text.html.php +++ b/src/Symfony/Component/ErrorHandler/Resources/views/traces_text.html.php @@ -20,15 +20,15 @@
 escape($exception['class']).":\n";
                     if ($exception['message']) {
-                        echo $exception['message']."\n";
+                        echo $this->escape($exception['message'])."\n";
                     }
 
                     foreach ($exception['trace'] as $trace) {
                         echo "\n  ";
                         if ($trace['function']) {
-                            echo 'at '.$trace['class'].$trace['type'].$trace['function'].'('.(isset($trace['args']) ? $this->formatArgsAsText($trace['args']) : '').')';
+                            echo $this->escape('at '.$trace['class'].$trace['type'].$trace['function']).'('.(isset($trace['args']) ? $this->formatArgsAsText($trace['args']) : '').')';
                         }
                         if ($trace['file'] && $trace['line']) {
                             echo($trace['function'] ? "\n     (" : 'at ').strtr(strip_tags($this->formatFile($trace['file'], $trace['line'])), [' at line '.$trace['line'] => '']).':'.$trace['line'].($trace['function'] ? ')' : '');
diff --git a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php
index 5394854738af5..d0ff0afba8710 100644
--- a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php
+++ b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php
@@ -289,12 +289,12 @@ class_exists(Fixtures\SubClassWithAnnotatedParameters::class, true);
 
         $this->assertSame([
             'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::quzMethod()" method will require a new "Quz $quz" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\ClassWithAnnotatedParameters", not defining it is deprecated.',
-            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::whereAmI()" method will require a new "bool $matrix" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
-            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "$noType" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
-            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "callable(\Throwable|null $reason, mixed $value) $callback" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
-            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "string $param" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
-            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "callable  ($a,  $b) $anotherOne" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
-            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "Type$WithDollarIsStillAType $ccc" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::whereAmI()" method will require a new "bool $matrix" argument in the next major version of its interface "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "$noType" argument in the next major version of its interface "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "callable(\Throwable|null $reason, mixed $value) $callback" argument in the next major version of its interface "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "string $param" argument in the next major version of its interface "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "callable  ($a,  $b) $anotherOne" argument in the next major version of its interface "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+            'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "Type$WithDollarIsStillAType $ccc" argument in the next major version of its interface "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
             'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::isSymfony()" method will require a new "true $yes" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\ClassWithAnnotatedParameters", not defining it is deprecated.',
         ], $deprecations);
     }
@@ -325,6 +325,7 @@ class_exists('Test\\'.ExtendsVirtual::class, true);
         restore_error_handler();
 
         $this->assertSame([
+            'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::staticReturningMethod()".',
             'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::sameLineInterfaceMethodNoBraces()".',
             'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::newLineInterfaceMethod()": Some description!',
             'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::newLineInterfaceMethodNoBraces()": Description.',
@@ -371,8 +372,8 @@ class_exists('Test\\'.ReturnType::class, true);
 
         $this->assertSame([
            'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeGrandParent::returnTypeGrandParent()" will return "string" as of its next major version. Doing the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" will be required when upgrading.',
-           'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParentInterface::returnTypeParentInterface()" will return "string" as of its next major version. Doing the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" will be required when upgrading.',
-           'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeInterface::returnTypeInterface()" will return "string" as of its next major version. Doing the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" will be required when upgrading.',
+           'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParentInterface::returnTypeParentInterface()" will return "string" as of its next major version. Doing the same in implementation "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" will be required when upgrading.',
+           'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeInterface::returnTypeInterface()" will return "string" as of its next major version. Doing the same in implementation "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" will be required when upgrading.',
            'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::oneNonNullableReturnableType()" will return "void" as of its next major version. Doing the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" will be required when upgrading.',
            'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::oneNonNullableReturnableTypeWithNull()" will return "void" as of its next major version. Doing the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" will be required when upgrading.',
            'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::oneNullableReturnableType()" will return "array" as of its next major version. Doing the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" will be required when upgrading.',
diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php
index 747dbb72dbc1f..70710302fd970 100644
--- a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php
+++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php
@@ -615,4 +615,39 @@ public function errorHandlerWhenLoggingProvider(): iterable
             }
         }
     }
+
+    public function testAssertQuietEval()
+    {
+        $ini = [
+            ini_set('zend.assertions', 1),
+            ini_set('assert.active', 1),
+            ini_set('assert.bail', 0),
+            ini_set('assert.warning', 1),
+            ini_set('assert.callback', null),
+            ini_set('assert.exception', 0),
+        ];
+
+        $logger = new BufferingLogger();
+        $handler = new ErrorHandler($logger);
+        $handler = ErrorHandler::register($handler);
+
+        try {
+            \assert(false);
+        } finally {
+            restore_error_handler();
+            restore_exception_handler();
+
+            ini_set('zend.assertions', $ini[0]);
+            ini_set('assert.active', $ini[1]);
+            ini_set('assert.bail', $ini[2]);
+            ini_set('assert.warning', $ini[3]);
+            ini_set('assert.callback', $ini[4]);
+            ini_set('assert.exception', $ini[5]);
+        }
+
+        $logs = $logger->cleanLogs();
+
+        $this->assertSame('warning', $logs[0][0]);
+        $this->assertSame('Warning: assert(): assert(false) failed', $logs[0][1]);
+    }
 }
diff --git a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/VirtualInterface.php b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/VirtualInterface.php
index fd1c8ba04edf8..5c9136081fe70 100644
--- a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/VirtualInterface.php
+++ b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/VirtualInterface.php
@@ -4,6 +4,7 @@
 
 /**
  * @method string interfaceMethod()
+ * @method static staticReturningMethod()
  * @method        sameLineInterfaceMethod($arg)
  * @method sameLineInterfaceMethodNoBraces
  *
@@ -25,7 +26,7 @@
  *
  * Static
  * @method static Foo&Bar staticMethod()
- * @method static staticMethodNoBraces
+ * @method static mixed staticMethodNoBraces
  * @method static \stdClass staticMethodTyped(int $arg) Description
  * @method static \stdClass[] staticMethodTypedNoBraces
  */
diff --git a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php
index 7820d35c31311..2bbebb7d6f794 100644
--- a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php
+++ b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php
@@ -16,7 +16,6 @@
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
 use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\EventDispatcher\Event as LegacyEvent;
 use Symfony\Component\EventDispatcher\EventDispatcher;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Symfony\Contracts\EventDispatcher\Event;
@@ -141,7 +140,6 @@ private function getEventFromTypeDeclaration(ContainerBuilder $container, string
             || !($type = $m->getParameters()[0]->getType())
             || $type->isBuiltin()
             || Event::class === ($name = $type->getName())
-            || LegacyEvent::class === $name
         ) {
             throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
         }
diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcher.php b/src/Symfony/Component/EventDispatcher/EventDispatcher.php
index c639e0b585082..8dba33d0d5278 100644
--- a/src/Symfony/Component/EventDispatcher/EventDispatcher.php
+++ b/src/Symfony/Component/EventDispatcher/EventDispatcher.php
@@ -37,7 +37,7 @@ class EventDispatcher implements EventDispatcherInterface
 
     public function __construct()
     {
-        if (__CLASS__ === \get_class($this)) {
+        if (__CLASS__ === static::class) {
             $this->optimized = [];
         }
     }
diff --git a/src/Symfony/Component/ExpressionLanguage/Lexer.php b/src/Symfony/Component/ExpressionLanguage/Lexer.php
index 0b36dbe79ead8..9dd695bf170d9 100644
--- a/src/Symfony/Component/ExpressionLanguage/Lexer.php
+++ b/src/Symfony/Component/ExpressionLanguage/Lexer.php
@@ -71,7 +71,7 @@ public function tokenize(string $expression)
                 // strings
                 $tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1);
                 $cursor += \strlen($match[0]);
-            } elseif (preg_match('/not in(?=[\s(])|\!\=\=|not(?=[\s(])|and(?=[\s(])|\=\=\=|\>\=|or(?=[\s(])|\<\=|\*\*|\.\.|in(?=[\s(])|&&|\|\||matches|\=\=|\!\=|\*|~|%|\/|\>|\||\!|\^|&|\+|\<|\-/A', $expression, $match, 0, $cursor)) {
+            } elseif (preg_match('/(?<=^|[\s(])not in(?=[\s(])|\!\=\=|(?<=^|[\s(])not(?=[\s(])|(?<=^|[\s(])and(?=[\s(])|\=\=\=|\>\=|(?<=^|[\s(])or(?=[\s(])|\<\=|\*\*|\.\.|(?<=^|[\s(])in(?=[\s(])|&&|\|\||(?<=^|[\s(])matches|\=\=|\!\=|\*|~|%|\/|\>|\||\!|\^|&|\+|\<|\-/A', $expression, $match, 0, $cursor)) {
                 // operators
                 $tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1);
                 $cursor += \strlen($match[0]);
diff --git a/src/Symfony/Component/ExpressionLanguage/Node/Node.php b/src/Symfony/Component/ExpressionLanguage/Node/Node.php
index 7eb2c656c837c..dc76de00bf0b5 100644
--- a/src/Symfony/Component/ExpressionLanguage/Node/Node.php
+++ b/src/Symfony/Component/ExpressionLanguage/Node/Node.php
@@ -43,7 +43,7 @@ public function __toString()
             $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
         }
 
-        $repr = [str_replace('Symfony\Component\ExpressionLanguage\Node\\', '', \get_class($this)).'('.implode(', ', $attributes)];
+        $repr = [str_replace('Symfony\Component\ExpressionLanguage\Node\\', '', static::class).'('.implode(', ', $attributes)];
 
         if (\count($this->nodes)) {
             foreach ($this->nodes as $node) {
@@ -79,7 +79,7 @@ public function evaluate(array $functions, array $values)
 
     public function toArray()
     {
-        throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', \get_class($this)));
+        throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', static::class));
     }
 
     public function dump()
diff --git a/src/Symfony/Component/ExpressionLanguage/Resources/bin/generate_operator_regex.php b/src/Symfony/Component/ExpressionLanguage/Resources/bin/generate_operator_regex.php
index e1196c7f514af..c86e962526182 100644
--- a/src/Symfony/Component/ExpressionLanguage/Resources/bin/generate_operator_regex.php
+++ b/src/Symfony/Component/ExpressionLanguage/Resources/bin/generate_operator_regex.php
@@ -15,9 +15,13 @@
 
 $regex = [];
 foreach ($operators as $operator => $length) {
-    // an operator that ends with a character must be followed by
-    // a whitespace or a parenthesis
-    $regex[] = preg_quote($operator, '/').(ctype_alpha($operator[$length - 1]) ? '(?=[\s(])' : '');
+    // Collisions of character operators:
+    // - an operator that begins with a character must have a space or a parenthesis before or starting at the beginning of a string
+    // - an operator that ends with a character must be followed by a whitespace or a parenthesis
+    $regex[] =
+        (ctype_alpha($operator[0]) ? '(?<=^|[\s(])' : '')
+        .preg_quote($operator, '/')
+        .(ctype_alpha($operator[$length - 1]) ? '(?=[\s(])' : '');
 }
 
 echo '/'.implode('|', $regex).'/A';
diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php
index 6e01b250caca5..9c7b30dc225d9 100644
--- a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php
+++ b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php
@@ -195,6 +195,17 @@ public function testCachingWithDifferentNamesOrder()
         $expressionLanguage->compile($expression, ['B' => 'b', 'a']);
     }
 
+    public function testOperatorCollisions()
+    {
+        $expressionLanguage = new ExpressionLanguage();
+        $expression = 'foo.not in [bar]';
+        $compiled = $expressionLanguage->compile($expression, ['foo', 'bar']);
+        $this->assertSame('in_array($foo->not, [0 => $bar])', $compiled);
+
+        $result = $expressionLanguage->evaluate($expression, ['foo' => (object) ['not' => 'test'], 'bar' => 'test']);
+        $this->assertTrue($result);
+    }
+
     /**
      * @dataProvider getRegisterCallbacks
      */
diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php
index 2674752aa25c5..35aead6862bad 100644
--- a/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php
+++ b/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php
@@ -114,6 +114,18 @@ public function getTokenizeData()
                 [new Token('string', '#foo', 1)],
                 '"#foo"',
             ],
+            [
+                [
+                    new Token('name', 'foo', 1),
+                    new Token('punctuation', '.', 4),
+                    new Token('name', 'not', 5),
+                    new Token('operator', 'in', 9),
+                    new Token('punctuation', '[', 12),
+                    new Token('name', 'bar', 13),
+                    new Token('punctuation', ']', 16),
+                ],
+                'foo.not in [bar]',
+            ],
         ];
     }
 }
diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php
index 84b30dc151cf4..2d5a0a6c8c817 100644
--- a/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php
+++ b/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php
@@ -53,6 +53,9 @@ public function getParseData()
         $arguments->addElement(new Node\ConstantNode(2));
         $arguments->addElement(new Node\ConstantNode(true));
 
+        $arrayNode = new Node\ArrayNode();
+        $arrayNode->addElement(new Node\NameNode('bar'));
+
         return [
             [
                 new Node\NameNode('a'),
@@ -151,6 +154,36 @@ public function getParseData()
                 'bar',
                 ['foo' => 'bar'],
             ],
+
+            // Operators collisions
+            [
+                new Node\BinaryNode(
+                    'in',
+                    new Node\GetAttrNode(
+                        new Node\NameNode('foo'),
+                        new Node\ConstantNode('not', true),
+                        new Node\ArgumentsNode(),
+                        Node\GetAttrNode::PROPERTY_CALL
+                    ),
+                    $arrayNode
+                ),
+                'foo.not in [bar]',
+                ['foo', 'bar'],
+            ],
+            [
+                new Node\BinaryNode(
+                    'or',
+                    new Node\UnaryNode('not', new Node\NameNode('foo')),
+                    new Node\GetAttrNode(
+                        new Node\NameNode('foo'),
+                        new Node\ConstantNode('not', true),
+                        new Node\ArgumentsNode(),
+                        Node\GetAttrNode::PROPERTY_CALL
+                    )
+                ),
+                'not foo or foo.not',
+                ['foo'],
+            ],
         ];
     }
 
diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php
index da1c6ab7fd902..e1bcea35d2656 100644
--- a/src/Symfony/Component/Finder/Finder.php
+++ b/src/Symfony/Component/Finder/Finder.php
@@ -782,6 +782,10 @@ private function searchInDirectory(string $dir): \Iterator
      */
     private function normalizeDir(string $dir): string
     {
+        if ('/' === $dir) {
+            return $dir;
+        }
+
         $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);
 
         if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
diff --git a/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php b/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php
index 3facef58a04f5..7616b14a245b5 100644
--- a/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php
+++ b/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php
@@ -70,7 +70,11 @@ public function current()
         }
         $subPathname .= $this->getFilename();
 
-        return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname);
+        if ('/' !== $basePath = $this->rootPath) {
+            $basePath .= $this->directorySeparator;
+        }
+
+        return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
     }
 
     /**
diff --git a/src/Symfony/Component/Form/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php
index 1a9cfd75be7f6..be3c87d9ca612 100644
--- a/src/Symfony/Component/Form/AbstractType.php
+++ b/src/Symfony/Component/Form/AbstractType.php
@@ -52,7 +52,7 @@ public function configureOptions(OptionsResolver $resolver)
      */
     public function getBlockPrefix()
     {
-        return StringUtil::fqcnToBlockPrefix(\get_class($this)) ?: '';
+        return StringUtil::fqcnToBlockPrefix(static::class) ?: '';
     }
 
     /**
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
index 8e0e1454e01d2..59d59093e09e1 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
@@ -279,7 +279,7 @@ private function round($number)
                     break;
             }
 
-            $number /= $roundingCoef;
+            $number = 1 === $roundingCoef ? (int) $number : $number / $roundingCoef;
         }
 
         return $number;
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
index fd9d8cd97f418..d26d0fd5752bb 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
@@ -109,7 +109,17 @@ public function buildForm(FormBuilderInterface $builder, array $options)
                 'invalid_message_parameters',
             ]));
 
-            if (isset($emptyData['date'])) {
+            if ($emptyData instanceof \Closure) {
+                $lazyEmptyData = static function ($option) use ($emptyData) {
+                    return static function (FormInterface $form) use ($emptyData, $option) {
+                        $emptyData = $emptyData($form->getParent());
+
+                        return isset($emptyData[$option]) ? $emptyData[$option] : '';
+                    };
+                };
+
+                $dateOptions['empty_data'] = $lazyEmptyData('date');
+            } elseif (isset($emptyData['date'])) {
                 $dateOptions['empty_data'] = $emptyData['date'];
             }
 
@@ -128,7 +138,9 @@ public function buildForm(FormBuilderInterface $builder, array $options)
                 'invalid_message_parameters',
             ]));
 
-            if (isset($emptyData['time'])) {
+            if ($emptyData instanceof \Closure) {
+                $timeOptions['empty_data'] = $lazyEmptyData('time');
+            } elseif (isset($emptyData['time'])) {
                 $timeOptions['empty_data'] = $emptyData['time'];
             }
 
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
index 1b320e6970bff..a187798cdbd97 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
@@ -83,14 +83,28 @@ public function buildForm(FormBuilderInterface $builder, array $options)
             // so we need to handle the cascade setting here
             $emptyData = $builder->getEmptyData() ?: [];
 
-            if (isset($emptyData['year'])) {
-                $yearOptions['empty_data'] = $emptyData['year'];
-            }
-            if (isset($emptyData['month'])) {
-                $monthOptions['empty_data'] = $emptyData['month'];
-            }
-            if (isset($emptyData['day'])) {
-                $dayOptions['empty_data'] = $emptyData['day'];
+            if ($emptyData instanceof \Closure) {
+                $lazyEmptyData = static function ($option) use ($emptyData) {
+                    return static function (FormInterface $form) use ($emptyData, $option) {
+                        $emptyData = $emptyData($form->getParent());
+
+                        return isset($emptyData[$option]) ? $emptyData[$option] : '';
+                    };
+                };
+
+                $yearOptions['empty_data'] = $lazyEmptyData('year');
+                $monthOptions['empty_data'] = $lazyEmptyData('month');
+                $dayOptions['empty_data'] = $lazyEmptyData('day');
+            } else {
+                if (isset($emptyData['year'])) {
+                    $yearOptions['empty_data'] = $emptyData['year'];
+                }
+                if (isset($emptyData['month'])) {
+                    $monthOptions['empty_data'] = $emptyData['month'];
+                }
+                if (isset($emptyData['day'])) {
+                    $dayOptions['empty_data'] = $emptyData['day'];
+                }
             }
 
             if (isset($options['invalid_message'])) {
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
index ed683beeaf981..b5c035f46246e 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
@@ -94,7 +94,17 @@ public function buildForm(FormBuilderInterface $builder, array $options)
             // so we need to handle the cascade setting here
             $emptyData = $builder->getEmptyData() ?: [];
 
-            if (isset($emptyData['hour'])) {
+            if ($emptyData instanceof \Closure) {
+                $lazyEmptyData = static function ($option) use ($emptyData) {
+                    return static function (FormInterface $form) use ($emptyData, $option) {
+                        $emptyData = $emptyData($form->getParent());
+
+                        return isset($emptyData[$option]) ? $emptyData[$option] : '';
+                    };
+                };
+
+                $hourOptions['empty_data'] = $lazyEmptyData('hour');
+            } elseif (isset($emptyData['hour'])) {
                 $hourOptions['empty_data'] = $emptyData['hour'];
             }
 
@@ -161,14 +171,18 @@ public function buildForm(FormBuilderInterface $builder, array $options)
             $builder->add('hour', self::$widgets[$options['widget']], $hourOptions);
 
             if ($options['with_minutes']) {
-                if (isset($emptyData['minute'])) {
+                if ($emptyData instanceof \Closure) {
+                    $minuteOptions['empty_data'] = $lazyEmptyData('minute');
+                } elseif (isset($emptyData['minute'])) {
                     $minuteOptions['empty_data'] = $emptyData['minute'];
                 }
                 $builder->add('minute', self::$widgets[$options['widget']], $minuteOptions);
             }
 
             if ($options['with_seconds']) {
-                if (isset($emptyData['second'])) {
+                if ($emptyData instanceof \Closure) {
+                    $secondOptions['empty_data'] = $lazyEmptyData('second');
+                } elseif (isset($emptyData['second'])) {
                     $secondOptions['empty_data'] = $emptyData['second'];
                 }
                 $builder->add('second', self::$widgets[$options['widget']], $secondOptions);
diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
index 1a91b2e2a362b..9c5594bcb8dd6 100644
--- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
@@ -120,7 +120,7 @@ abstract protected function renderLabel(FormView $view, $label = null, array $va
 
     protected function renderHelp(FormView $view)
     {
-        $this->markTestSkipped(sprintf('%s::renderHelp() is not implemented.', \get_class($this)));
+        $this->markTestSkipped(sprintf('%s::renderHelp() is not implemented.', static::class));
     }
 
     abstract protected function renderErrors(FormView $view);
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php
index cb3f5d890ed1a..80da74ed56455 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php
@@ -370,7 +370,7 @@ public function testReverseTransformWithRounding($scale, $input, $output, $round
     {
         $transformer = new NumberToLocalizedStringTransformer($scale, null, $roundingMode);
 
-        $this->assertEquals($output, $transformer->reverseTransform($input));
+        $this->assertSame($output, $transformer->reverseTransform($input));
     }
 
     public function testReverseTransformDoesNotRoundIfNoScale()
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php
index e21692fcafcd8..afa941dccce32 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php
@@ -12,6 +12,7 @@
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
 use Symfony\Component\Form\FormError;
+use Symfony\Component\Form\FormInterface;
 
 class DateTimeTypeTest extends BaseTypeTest
 {
@@ -659,6 +660,9 @@ public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedDa
         ]);
         $form->submit(null);
 
+        if ($emptyData instanceof \Closure) {
+            $emptyData = $emptyData($form);
+        }
         $this->assertSame($emptyData, $form->getViewData());
         $this->assertEquals($expectedData, $form->getNormData());
         $this->assertEquals($expectedData, $form->getData());
@@ -667,11 +671,17 @@ public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedDa
     public function provideEmptyData()
     {
         $expectedData = \DateTime::createFromFormat('Y-m-d H:i', '2018-11-11 21:23');
+        $lazyEmptyData = static function (FormInterface $form) {
+            return $form->getConfig()->getCompound() ? ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']] : '2018-11-11T21:23:00';
+        };
 
         return [
             'Simple field' => ['single_text', '2018-11-11T21:23:00', $expectedData],
             'Compound text field' => ['text', ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']], $expectedData],
             'Compound choice field' => ['choice', ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']], $expectedData],
+            'Simple field lazy' => ['single_text', $lazyEmptyData, $expectedData],
+            'Compound text field lazy' => ['text', $lazyEmptyData, $expectedData],
+            'Compound choice field lazy' => ['choice', $lazyEmptyData, $expectedData],
         ];
     }
 
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php
index 62588011da225..a281215fd4a09 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php
@@ -13,6 +13,7 @@
 
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
 use Symfony\Component\Form\FormError;
+use Symfony\Component\Form\FormInterface;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
 class DateTypeTest extends BaseTypeTest
@@ -1022,6 +1023,9 @@ public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedDa
         ]);
         $form->submit(null);
 
+        if ($emptyData instanceof \Closure) {
+            $emptyData = $emptyData($form);
+        }
         $this->assertSame($emptyData, $form->getViewData());
         $this->assertEquals($expectedData, $form->getNormData());
         $this->assertEquals($expectedData, $form->getData());
@@ -1030,11 +1034,17 @@ public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedDa
     public function provideEmptyData()
     {
         $expectedData = \DateTime::createFromFormat('Y-m-d H:i:s', '2018-11-11 00:00:00');
+        $lazyEmptyData = static function (FormInterface $form) {
+            return $form->getConfig()->getCompound() ? ['year' => '2018', 'month' => '11', 'day' => '11'] : '2018-11-11';
+        };
 
         return [
             'Simple field' => ['single_text', '2018-11-11', $expectedData],
             'Compound text fields' => ['text', ['year' => '2018', 'month' => '11', 'day' => '11'], $expectedData],
             'Compound choice fields' => ['choice', ['year' => '2018', 'month' => '11', 'day' => '11'], $expectedData],
+            'Simple field lazy' => ['single_text', $lazyEmptyData, $expectedData],
+            'Compound text fields lazy' => ['text', $lazyEmptyData, $expectedData],
+            'Compound choice fields lazy' => ['choice', $lazyEmptyData, $expectedData],
         ];
     }
 
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php
index f220bf97e8a6d..5453667a26853 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php
@@ -13,6 +13,7 @@
 
 use Symfony\Component\Form\ChoiceList\View\ChoiceView;
 use Symfony\Component\Form\FormError;
+use Symfony\Component\Form\FormInterface;
 
 class TimeTypeTest extends BaseTypeTest
 {
@@ -928,6 +929,9 @@ public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedDa
         ]);
         $form->submit(null);
 
+        if ($emptyData instanceof \Closure) {
+            $emptyData = $emptyData($form);
+        }
         $this->assertSame($emptyData, $form->getViewData());
         $this->assertEquals($expectedData, $form->getNormData());
         $this->assertEquals($expectedData, $form->getData());
@@ -936,11 +940,17 @@ public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedDa
     public function provideEmptyData()
     {
         $expectedData = \DateTime::createFromFormat('Y-m-d H:i', '1970-01-01 21:23');
+        $lazyEmptyData = static function (FormInterface $form) {
+            return $form->getConfig()->getCompound() ? ['hour' => '21', 'minute' => '23'] : '21:23';
+        };
 
         return [
             'Simple field' => ['single_text', '21:23', $expectedData],
             'Compound text field' => ['text', ['hour' => '21', 'minute' => '23'], $expectedData],
             'Compound choice field' => ['choice', ['hour' => '21', 'minute' => '23'], $expectedData],
+            'Simple field lazy' => ['single_text', $lazyEmptyData, $expectedData],
+            'Compound text field lazy' => ['text', $lazyEmptyData, $expectedData],
+            'Compound choice field lazy' => ['choice', $lazyEmptyData, $expectedData],
         ];
     }
 }
diff --git a/src/Symfony/Component/HttpClient/CachingHttpClient.php b/src/Symfony/Component/HttpClient/CachingHttpClient.php
index 367140485f8ae..a6af1b5f2d5f1 100644
--- a/src/Symfony/Component/HttpClient/CachingHttpClient.php
+++ b/src/Symfony/Component/HttpClient/CachingHttpClient.php
@@ -55,6 +55,8 @@ public function __construct(HttpClientInterface $client, StoreInterface $store,
         unset($defaultOptions['allow_revalidate']);
         unset($defaultOptions['stale_while_revalidate']);
         unset($defaultOptions['stale_if_error']);
+        unset($defaultOptions['trace_level']);
+        unset($defaultOptions['trace_header']);
 
         if ($defaultOptions) {
             [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
diff --git a/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php b/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php
index fb9228b10610d..f125d1a59ba9a 100644
--- a/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php
+++ b/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php
@@ -114,11 +114,11 @@ private function collectOnClient(TraceableHttpClient $client): array
 
             unset($info['filetime'], $info['http_code'], $info['ssl_verify_result'], $info['content_type']);
 
-            if ($trace['method'] === $info['http_method']) {
+            if (($info['http_method'] ?? null) === $trace['method']) {
                 unset($info['http_method']);
             }
 
-            if ($trace['url'] === $info['url']) {
+            if (($info['url'] ?? null) === $trace['url']) {
                 unset($info['url']);
             }
 
diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php
index 2486f04bc8c8c..3eeb52fdd5cea 100644
--- a/src/Symfony/Component/HttpClient/HttpClientTrait.php
+++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php
@@ -341,7 +341,7 @@ private static function jsonEncode($value, int $flags = null, int $maxDepth = 51
         $flags = $flags ?? (JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_PRESERVE_ZERO_FRACTION);
 
         try {
-            $value = json_encode($value, $flags | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0), $maxDepth);
+            $value = json_encode($value, $flags | (\PHP_VERSION_ID >= 70300 ? JSON_THROW_ON_ERROR : 0), $maxDepth);
         } catch (\JsonException $e) {
             throw new InvalidArgumentException(sprintf('Invalid value for "json" option: %s.', $e->getMessage()));
         }
diff --git a/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php b/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php
index 3f287feb6b80d..f17a4a78503c3 100644
--- a/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php
+++ b/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php
@@ -72,7 +72,7 @@ public function wait(?ResponseInterface $pendingResponse, float $maxDuration = n
                         goto check_duration;
                     }
 
-                    if ([$request, $promise] = $this->promisePool[$response] ?? null) {
+                    if ([, $promise] = $this->promisePool[$response] ?? null) {
                         unset($this->promisePool[$response]);
                         $promise->resolve($this->createPsr7Response($response, true));
                     }
diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php
index e1699b9b2b7ba..0a6597fb0852a 100644
--- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php
+++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php
@@ -16,6 +16,7 @@
 use Symfony\Component\HttpClient\Chunk\InformationalChunk;
 use Symfony\Component\HttpClient\Exception\TransportException;
 use Symfony\Component\HttpClient\Internal\CurlClientState;
+use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
 use Symfony\Contracts\HttpClient\ResponseInterface;
 
 /**
@@ -113,7 +114,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
         $this->initializer = static function (self $response) {
             $waitFor = curl_getinfo($ch = $response->handle, CURLINFO_PRIVATE);
 
-            return 'H' === $waitFor[0] || 'D' === $waitFor[0];
+            return 'H' === $waitFor[0];
         };
 
         // Schedule the request in a non-blocking way
@@ -174,17 +175,15 @@ public function __destruct()
                 return; // Unused pushed response
             }
 
-            $waitFor = curl_getinfo($this->handle, CURLINFO_PRIVATE);
-
-            if ('C' === $waitFor[0] || '_' === $waitFor[0]) {
-                $this->close();
-            } elseif ('H' === $waitFor[0]) {
-                $waitFor[0] = 'D'; // D = destruct
-                curl_setopt($this->handle, CURLOPT_PRIVATE, $waitFor);
-            }
-
+            $e = null;
             $this->doDestruct();
+        } catch (HttpExceptionInterface $e) {
+            throw $e;
         } finally {
+            if ($e ?? false) {
+                throw $e;
+            }
+
             $this->close();
 
             if (!$this->multi->openHandles) {
@@ -262,7 +261,7 @@ private static function perform(CurlClientState $multi, array &$responses = null
                 $id = (int) $ch = $info['handle'];
                 $waitFor = @curl_getinfo($ch, CURLINFO_PRIVATE) ?: '_0';
 
-                if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /*CURLE_HTTP2*/ 16, /*CURLE_HTTP2_STREAM*/ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
+                if (\in_array($result, [CURLE_SEND_ERROR, CURLE_RECV_ERROR, /*CURLE_HTTP2*/ 16, /*CURLE_HTTP2_STREAM*/ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
                     curl_multi_remove_handle($multi->handle, $ch);
                     $waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
                     curl_setopt($ch, CURLOPT_PRIVATE, $waitFor);
@@ -277,7 +276,7 @@ private static function perform(CurlClientState $multi, array &$responses = null
                 }
 
                 $multi->handlesActivity[$id][] = null;
-                $multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(sprintf('%s for "%s".', curl_strerror($result), curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)));
+                $multi->handlesActivity[$id][] = \in_array($result, [CURLE_OK, CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(sprintf('%s for "%s".', curl_strerror($result), curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)));
             }
         } finally {
             self::$performing = false;
@@ -304,7 +303,7 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
     {
         $waitFor = @curl_getinfo($ch, CURLINFO_PRIVATE) ?: '_0';
 
-        if ('H' !== $waitFor[0] && 'D' !== $waitFor[0]) {
+        if ('H' !== $waitFor[0]) {
             return \strlen($data); // Ignore HTTP trailers
         }
 
@@ -370,7 +369,7 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
             // Headers and redirects completed, time to get the response's content
             $multi->handlesActivity[$id][] = new FirstChunk();
 
-            if ('D' === $waitFor[0] || 'HEAD' === $info['http_method'] || \in_array($statusCode, [204, 304], true)) {
+            if ('HEAD' === $info['http_method'] || \in_array($statusCode, [204, 304], true)) {
                 $waitFor = '_0'; // no content expected
                 $multi->handlesActivity[$id][] = null;
                 $multi->handlesActivity[$id][] = null;
diff --git a/src/Symfony/Component/HttpClient/Response/NativeResponse.php b/src/Symfony/Component/HttpClient/Response/NativeResponse.php
index 7abe18abab2e4..6fccfbef5a6a8 100644
--- a/src/Symfony/Component/HttpClient/Response/NativeResponse.php
+++ b/src/Symfony/Component/HttpClient/Response/NativeResponse.php
@@ -15,6 +15,7 @@
 use Symfony\Component\HttpClient\Chunk\FirstChunk;
 use Symfony\Component\HttpClient\Exception\TransportException;
 use Symfony\Component\HttpClient\Internal\NativeClientState;
+use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
 use Symfony\Contracts\HttpClient\ResponseInterface;
 
 /**
@@ -84,11 +85,16 @@ public function getInfo(string $type = null)
 
     public function __destruct()
     {
-        $this->shouldBuffer = null;
-
         try {
+            $e = null;
             $this->doDestruct();
+        } catch (HttpExceptionInterface $e) {
+            throw $e;
         } finally {
+            if ($e ?? false) {
+                throw $e;
+            }
+
             $this->close();
 
             // Clear the DNS cache when all requests completed
diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php
index b7b576e564e8a..595ac9737c072 100644
--- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php
+++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php
@@ -147,21 +147,21 @@ public function toArray(bool $throw = true): array
         $contentType = $this->headers['content-type'][0] ?? 'application/json';
 
         if (!preg_match('/\bjson\b/i', $contentType)) {
-            throw new JsonException(sprintf('Response content-type is "%s" while a JSON-compatible one was expected.', $contentType));
+            throw new JsonException(sprintf('Response content-type is "%s" while a JSON-compatible one was expected for "%s".', $contentType, $this->getInfo('url')));
         }
 
         try {
-            $content = json_decode($content, true, 512, JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0));
+            $content = json_decode($content, true, 512, JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? JSON_THROW_ON_ERROR : 0));
         } catch (\JsonException $e) {
-            throw new JsonException($e->getMessage(), $e->getCode());
+            throw new JsonException(sprintf('%s for "%s".', $e->getMessage(), $this->getInfo('url')), $e->getCode());
         }
 
         if (\PHP_VERSION_ID < 70300 && JSON_ERROR_NONE !== json_last_error()) {
-            throw new JsonException(json_last_error_msg(), json_last_error());
+            throw new JsonException(sprintf('%s for "%s".', json_last_error_msg(), $this->getInfo('url')), json_last_error());
         }
 
         if (!\is_array($content)) {
-            throw new JsonException(sprintf('JSON content was expected to decode to an array, %s returned.', \gettype($content)));
+            throw new JsonException(sprintf('JSON content was expected to decode to an array, %s returned for "%s".', \gettype($content), $this->getInfo('url')));
         }
 
         if (null !== $this->content) {
@@ -294,6 +294,8 @@ private function checkStatusCode()
      */
     private function doDestruct()
     {
+        $this->shouldBuffer = true;
+
         if ($this->initializer && null === $this->info['error']) {
             self::initialize($this);
             $this->checkStatusCode();
diff --git a/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php b/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php
new file mode 100644
index 0000000000000..61ceb913a93e0
--- /dev/null
+++ b/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php
@@ -0,0 +1,68 @@
+ 'orange', 'size' => 42];
+        $response = new MockResponse(json_encode($data));
+        $response = MockResponse::fromRequest('GET', 'https://example.com/file.json', [], $response);
+
+        $this->assertSame($data, $response->toArray());
+    }
+
+    /**
+     * @dataProvider toArrayErrors
+     */
+    public function testToArrayError($content, $responseHeaders, $message)
+    {
+        $this->expectException(JsonException::class);
+        $this->expectExceptionMessage($message);
+
+        $response = new MockResponse($content, ['response_headers' => $responseHeaders]);
+        $response = MockResponse::fromRequest('GET', 'https://example.com/file.json', [], $response);
+        $response->toArray();
+    }
+
+    public function toArrayErrors()
+    {
+        yield [
+            'content' => '{}',
+            'responseHeaders' => ['content-type' => 'plain/text'],
+            'message' => 'Response content-type is "plain/text" while a JSON-compatible one was expected for "https://example.com/file.json".',
+        ];
+
+        yield [
+            'content' => 'not json',
+            'responseHeaders' => [],
+            'message' => 'Syntax error for "https://example.com/file.json".',
+        ];
+
+        yield [
+            'content' => '[1,2}',
+            'responseHeaders' => [],
+            'message' => 'State mismatch (invalid or malformed JSON) for "https://example.com/file.json".',
+        ];
+
+        yield [
+            'content' => '"not an array"',
+            'responseHeaders' => [],
+            'message' => 'JSON content was expected to decode to an array, string returned for "https://example.com/file.json".',
+        ];
+
+        yield [
+            'content' => '8',
+            'responseHeaders' => [],
+            'message' => 'JSON content was expected to decode to an array, integer returned for "https://example.com/file.json".',
+        ];
+    }
+}
diff --git a/src/Symfony/Component/HttpFoundation/RedirectResponse.php b/src/Symfony/Component/HttpFoundation/RedirectResponse.php
index 13da56a75c056..c6d7590ef168b 100644
--- a/src/Symfony/Component/HttpFoundation/RedirectResponse.php
+++ b/src/Symfony/Component/HttpFoundation/RedirectResponse.php
@@ -42,7 +42,7 @@ public function __construct(string $url, int $status = 302, array $headers = [])
             throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
         }
 
-        if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) {
+        if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, CASE_LOWER))) {
             $this->headers->remove('cache-control');
         }
     }
diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php
index 895d110f41c6a..4a331954bde47 100644
--- a/src/Symfony/Component/HttpFoundation/Response.php
+++ b/src/Symfony/Component/HttpFoundation/Response.php
@@ -258,10 +258,12 @@ public function prepare(Request $request)
             $this->setContent(null);
             $headers->remove('Content-Type');
             $headers->remove('Content-Length');
+            // prevent PHP from sending the Content-Type header based on default_mimetype
+            ini_set('default_mimetype', '');
         } else {
             // Content-type based on the Request
             if (!$headers->has('Content-Type')) {
-                $format = $request->getPreferredFormat();
+                $format = $request->getPreferredFormat(null);
                 if (null !== $format && $mimeType = $request->getMimeType($format)) {
                     $headers->set('Content-Type', $mimeType);
                 }
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php
index 0618035b4b1a3..bd151669c8c03 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php
@@ -114,7 +114,7 @@ public function destroy($sessionId)
     {
         if (!headers_sent() && filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN)) {
             if (!$this->sessionName) {
-                throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', \get_class($this)));
+                throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
             }
             $cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId);
 
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php
index 1f017c8afdceb..b92bd0ffde3b3 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php
@@ -54,7 +54,7 @@ public static function createHandler($connection): AbstractSessionHandler
             case 0 === strpos($connection, 'rediss:'):
             case 0 === strpos($connection, 'memcached:'):
                 if (!class_exists(AbstractAdapter::class)) {
-                    throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection));
+                    throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection));
                 }
                 $handlerClass = 0 === strpos($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class;
                 $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]);
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php
index eb754dbbfcfa4..792bacf4e4ca5 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php
@@ -139,7 +139,7 @@ public function start()
             return true;
         }
 
-        if (\PHP_SESSION_ACTIVE === session_status()) {
+        if (PHP_SESSION_ACTIVE === session_status()) {
             throw new \RuntimeException('Failed to start the session: already started by PHP.');
         }
 
@@ -202,7 +202,7 @@ public function setName(string $name)
     public function regenerate(bool $destroy = false, int $lifetime = null)
     {
         // Cannot regenerate the session ID for non-active sessions.
-        if (\PHP_SESSION_ACTIVE !== session_status()) {
+        if (PHP_SESSION_ACTIVE !== session_status()) {
             return false;
         }
 
@@ -362,7 +362,7 @@ public function isStarted()
      */
     public function setOptions(array $options)
     {
-        if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+        if (headers_sent() || PHP_SESSION_ACTIVE === session_status()) {
             return;
         }
 
@@ -427,7 +427,7 @@ public function setSaveHandler($saveHandler = null)
         }
         $this->saveHandler = $saveHandler;
 
-        if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+        if (headers_sent() || PHP_SESSION_ACTIVE === session_status()) {
             return;
         }
 
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php
index ded79d64a6b2e..38f7dede60e52 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php
@@ -65,7 +65,7 @@ public function isWrapper()
      */
     public function isActive()
     {
-        return \PHP_SESSION_ACTIVE === session_status();
+        return PHP_SESSION_ACTIVE === session_status();
     }
 
     /**
diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php
index 8139dcceeff4a..6e6a210eee105 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php
@@ -461,18 +461,10 @@ public function testSetVary()
 
     public function testDefaultContentType()
     {
-        $headerMock = $this->getMockBuilder('Symfony\Component\HttpFoundation\ResponseHeaderBag')->setMethods(['set'])->getMock();
-        $headerMock->expects($this->at(0))
-            ->method('set')
-            ->with('Content-Type', 'text/html');
-        $headerMock->expects($this->at(1))
-            ->method('set')
-            ->with('Content-Type', 'text/html; charset=UTF-8');
-
         $response = new Response('foo');
-        $response->headers = $headerMock;
-
         $response->prepare(new Request());
+
+        $this->assertSame('text/html; charset=UTF-8', $response->headers->get('Content-Type'));
     }
 
     public function testContentTypeCharset()
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php
index 206ff48777e95..b4fad768834ff 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php
@@ -61,12 +61,12 @@ public function testPhpSession()
     {
         $storage = $this->getStorage();
 
-        $this->assertNotSame(\PHP_SESSION_ACTIVE, session_status());
+        $this->assertNotSame(PHP_SESSION_ACTIVE, session_status());
         $this->assertFalse($storage->isStarted());
 
         session_start();
         $this->assertTrue(isset($_SESSION));
-        $this->assertSame(\PHP_SESSION_ACTIVE, session_status());
+        $this->assertSame(PHP_SESSION_ACTIVE, session_status());
         // PHP session might have started, but the storage driver has not, so false is correct here
         $this->assertFalse($storage->isStarted());
 
diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php
index b5bd403d153c1..4534de43ba652 100644
--- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php
+++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php
@@ -85,7 +85,7 @@ public function getController(Request $request)
         try {
             $callable = $this->createController($controller);
         } catch (\InvalidArgumentException $e) {
-            throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s', $request->getPathInfo(), $e->getMessage()));
+            throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s', $request->getPathInfo(), $e->getMessage()), 0, $e);
         }
 
         if (!\is_callable($callable)) {
diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php
index 596b6188f66cb..79e67374b2253 100644
--- a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php
+++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php
@@ -51,9 +51,6 @@ public function process(ContainerBuilder $container)
                     }
                 }
                 if (!$reason) {
-                    // Deprecated since Symfony 4.1. See Symfony\Component\HttpKernel\Controller\ContainerControllerResolver
-                    $controllers[$id.':'.$action] = $argumentRef;
-
                     if ('__invoke' === $action) {
                         $controllers[$id] = $argumentRef;
                     }
diff --git a/src/Symfony/Component/HttpKernel/Event/ExceptionEvent.php b/src/Symfony/Component/HttpKernel/Event/ExceptionEvent.php
index a10741a84d1f8..a18fbd31f4949 100644
--- a/src/Symfony/Component/HttpKernel/Event/ExceptionEvent.php
+++ b/src/Symfony/Component/HttpKernel/Event/ExceptionEvent.php
@@ -21,7 +21,7 @@
  * current request. The propagation of this event is stopped as soon as a
  * response is set.
  *
- * You can also call setException() to replace the thrown exception. This
+ * You can also call setThrowable() to replace the thrown exception. This
  * exception will be thrown if no response is set during processing of this
  * event.
  *
diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php
index fbb1f2e76c2ea..5efd7621712e6 100644
--- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php
+++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php
@@ -64,6 +64,9 @@ public function __construct(callable $exceptionHandler = null, LoggerInterface $
      */
     public function configure(object $event = null)
     {
+        if ($event instanceof ConsoleEvent && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
+            return;
+        }
         if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMasterRequest()) {
             return;
         }
@@ -144,7 +147,7 @@ public static function getSubscribedEvents(): array
     {
         $events = [KernelEvents::REQUEST => ['configure', 2048]];
 
-        if ('cli' === \PHP_SAPI && \defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
+        if (\defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
             $events[ConsoleEvents::COMMAND] = ['configure', 2048];
         }
 
diff --git a/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php
index 9db6557663c8e..618859d0f9e0b 100644
--- a/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php
+++ b/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php
@@ -16,9 +16,6 @@
 use Symfony\Component\HttpKernel\Controller\ControllerReference;
 use Symfony\Component\HttpKernel\UriSigner;
 use Twig\Environment;
-use Twig\Error\LoaderError;
-use Twig\Loader\ExistsLoaderInterface;
-use Twig\Loader\SourceContextLoaderInterface;
 
 /**
  * Implements the Hinclude rendering strategy.
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index 123d46842ab97..ce30c8feba2e0 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -68,11 +68,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
 
     private static $freshCache = [];
 
-    const VERSION = '5.0.4';
-    const VERSION_ID = 50004;
+    const VERSION = '5.0.5';
+    const VERSION_ID = 50005;
     const MAJOR_VERSION = 5;
     const MINOR_VERSION = 0;
-    const RELEASE_VERSION = 4;
+    const RELEASE_VERSION = 5;
     const EXTRA_VERSION = '';
 
     const END_OF_MAINTENANCE = '07/2020';
@@ -218,7 +218,7 @@ public function getBundles()
     public function getBundle(string $name)
     {
         if (!isset($this->bundles[$name])) {
-            $class = \get_class($this);
+            $class = static::class;
             $class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
 
             throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the registerBundles() method of your %s.php file?', $name, $class));
@@ -393,7 +393,7 @@ protected function build(ContainerBuilder $container)
      */
     protected function getContainerClass()
     {
-        $class = \get_class($this);
+        $class = static::class;
         $class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class)) : $class;
         $class = str_replace('\\', '_', $class).ucfirst($this->environment).($this->debug ? 'Debug' : '').'Container';
         if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) {
@@ -429,7 +429,7 @@ protected function initializeContainer()
         $cachePath = $cache->getPath();
 
         // Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
-        $errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
+        $errorLevel = error_reporting(E_ALL ^ E_WARNING);
 
         try {
             if (file_exists($cachePath) && \is_object($this->container = include $cachePath)
diff --git a/src/Symfony/Component/HttpKernel/Log/Logger.php b/src/Symfony/Component/HttpKernel/Log/Logger.php
index c27bb3f07048c..6ea3b464f0d5e 100644
--- a/src/Symfony/Component/HttpKernel/Log/Logger.php
+++ b/src/Symfony/Component/HttpKernel/Log/Logger.php
@@ -101,6 +101,6 @@ private function format(string $level, string $message, array $context): string
             $message = strtr($message, $replacements);
         }
 
-        return sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message).\PHP_EOL;
+        return sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message).PHP_EOL;
     }
 }
diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php
index a89aeeac1f0d5..5753dc88da7bc 100644
--- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php
@@ -99,7 +99,7 @@ public function provideControllerCallables()
                 '"Regular" callable',
                 [$this, 'testControllerInspection'],
                 [
-                    'class' => RequestDataCollectorTest::class,
+                    'class' => self::class,
                     'method' => 'testControllerInspection',
                     'file' => __FILE__,
                     'line' => $r1->getStartLine(),
diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php
index b5e55bdea9e97..6c840aedca306 100644
--- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php
@@ -49,7 +49,7 @@ public function testProcess()
 
         $controllers = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0);
 
-        $this->assertSame(['c1::fooAction', 'c1:fooAction'], array_keys($controllers));
+        $this->assertSame(['c1::fooAction'], array_keys($controllers));
         $this->assertSame(['bar'], array_keys($container->getDefinition((string) $controllers['c1::fooAction']->getValues()[0])->getArgument(0)));
 
         $expectedLog = [
@@ -73,7 +73,7 @@ public function testInvoke()
         (new RemoveEmptyControllerArgumentLocatorsPass())->process($container);
 
         $this->assertEquals(
-            ['invokable::__invoke', 'invokable:__invoke', 'invokable'],
+            ['invokable::__invoke', 'invokable'],
             array_keys($container->getDefinition((string) $resolver->getArgument(0))->getArgument(0))
         );
     }
diff --git a/src/Symfony/Component/HttpKernel/Tests/Log/LoggerTest.php b/src/Symfony/Component/HttpKernel/Tests/Log/LoggerTest.php
index c562af1bcb7a6..5c7da41957113 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Log/LoggerTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Log/LoggerTest.php
@@ -186,7 +186,7 @@ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
     public function testFormatter()
     {
         $this->logger = new Logger(LogLevel::DEBUG, $this->tmpFile, function ($level, $message, $context) {
-            return json_encode(['level' => $level, 'message' => $message, 'context' => $context]).\PHP_EOL;
+            return json_encode(['level' => $level, 'message' => $message, 'context' => $context]).PHP_EOL;
         });
 
         $this->logger->error('An error', ['foo' => 'bar']);
diff --git a/src/Symfony/Component/Intl/Collator/Collator.php b/src/Symfony/Component/Intl/Collator/Collator.php
index a43a4f69bf0eb..ff0d258e1d414 100644
--- a/src/Symfony/Component/Intl/Collator/Collator.php
+++ b/src/Symfony/Component/Intl/Collator/Collator.php
@@ -109,9 +109,9 @@ public static function create(?string $locale)
     public function asort(array &$array, int $sortFlag = self::SORT_REGULAR)
     {
         $intlToPlainFlagMap = [
-            self::SORT_REGULAR => \SORT_REGULAR,
-            self::SORT_NUMERIC => \SORT_NUMERIC,
-            self::SORT_STRING => \SORT_STRING,
+            self::SORT_REGULAR => SORT_REGULAR,
+            self::SORT_NUMERIC => SORT_NUMERIC,
+            self::SORT_STRING => SORT_STRING,
         ];
 
         $plainSortFlag = isset($intlToPlainFlagMap[$sortFlag]) ? $intlToPlainFlagMap[$sortFlag] : self::SORT_REGULAR;
diff --git a/src/Symfony/Component/Intl/Data/Generator/LocaleDataGenerator.php b/src/Symfony/Component/Intl/Data/Generator/LocaleDataGenerator.php
index 8cf0ae52abe13..9584dec23c4d5 100644
--- a/src/Symfony/Component/Intl/Data/Generator/LocaleDataGenerator.php
+++ b/src/Symfony/Component/Intl/Data/Generator/LocaleDataGenerator.php
@@ -67,7 +67,7 @@ protected function preGenerate()
         // Write parents locale file for the Translation component
         file_put_contents(
             __DIR__.'/../../../Translation/Resources/data/parents.json',
-            json_encode($this->localeParents, \JSON_PRETTY_PRINT).\PHP_EOL
+            json_encode($this->localeParents, JSON_PRETTY_PRINT).PHP_EOL
         );
     }
 
diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
index d10702483478e..0a0bea4a36ffb 100644
--- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
+++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
@@ -201,9 +201,9 @@ abstract class NumberFormatter
      * @see https://php.net/round
      */
     private static $phpRoundingMap = [
-        self::ROUND_HALFDOWN => \PHP_ROUND_HALF_DOWN,
-        self::ROUND_HALFEVEN => \PHP_ROUND_HALF_EVEN,
-        self::ROUND_HALFUP => \PHP_ROUND_HALF_UP,
+        self::ROUND_HALFDOWN => PHP_ROUND_HALF_DOWN,
+        self::ROUND_HALFEVEN => PHP_ROUND_HALF_EVEN,
+        self::ROUND_HALFUP => PHP_ROUND_HALF_UP,
     ];
 
     /**
@@ -354,7 +354,7 @@ public function format($value, int $type = self::TYPE_DEFAULT)
     {
         // The original NumberFormatter does not support this format type
         if (self::TYPE_CURRENCY === $type) {
-            trigger_error(__METHOD__.'(): Unsupported format type '.$type, \E_USER_WARNING);
+            trigger_error(__METHOD__.'(): Unsupported format type '.$type, E_USER_WARNING);
 
             return false;
         }
@@ -508,7 +508,7 @@ public function parseCurrency(string $value, string &$currency, int &$position =
     public function parse(string $value, int $type = self::TYPE_DOUBLE, int &$position = 0)
     {
         if (self::TYPE_DEFAULT === $type || self::TYPE_CURRENCY === $type) {
-            trigger_error(__METHOD__.'(): Unsupported format type '.$type, \E_USER_WARNING);
+            trigger_error(__METHOD__.'(): Unsupported format type '.$type, E_USER_WARNING);
 
             return false;
         }
diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php
index 4ec8bdc805113..726f1d6863468 100644
--- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php
+++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php
@@ -116,6 +116,10 @@ protected function configureOptions(OptionsResolver $resolver)
                 $options->setDefault('debug_level', 7);
             }
 
+            if (!isset($parent['network_timeout'])) {
+                $options->setDefault('network_timeout', ini_get('default_socket_timeout'));
+            }
+
             $options->setDefaults([
                 'protocol_version' => $parent['version'],
                 'referrals' => $parent['referrals'],
diff --git a/src/Symfony/Component/Lock/Store/PdoStore.php b/src/Symfony/Component/Lock/Store/PdoStore.php
index caef22cfea619..1fb1670f3b084 100644
--- a/src/Symfony/Component/Lock/Store/PdoStore.php
+++ b/src/Symfony/Component/Lock/Store/PdoStore.php
@@ -307,7 +307,7 @@ private function getDriver(): string
         } else {
             switch ($this->driver = $con->getDriver()->getName()) {
                 case 'mysqli':
-                    throw new NotSupportedException(sprintf('The store "%s" does not support the mysqli driver, use pdo_mysql instead.', \get_class($this)));
+                    throw new NotSupportedException(sprintf('The store "%s" does not support the mysqli driver, use pdo_mysql instead.', static::class));
                 case 'pdo_mysql':
                 case 'drizzle_pdo_mysql':
                     $this->driver = 'mysql';
diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillHttpTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillHttpTransportTest.php
index dd2851154e76d..cfdc30bb78e63 100644
--- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillHttpTransportTest.php
+++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Transport/MandrillHttpTransportTest.php
@@ -57,7 +57,7 @@ public function testSend()
             $body = json_decode($options['body'], true);
             $message = $body['raw_message'];
             $this->assertSame('KEY', $body['key']);
-            $this->assertSame('Saif Eddin ', $body['to'][0]);
+            $this->assertSame('saif.gmati@symfony.com', $body['to'][0]);
             $this->assertSame('Fabien ', $body['from_email']);
 
             $this->assertStringContainsString('Subject: Hello!', $message);
diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php
index 17f7a7fcf3364..6ead15490671f 100644
--- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php
+++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php
@@ -12,9 +12,11 @@
 namespace Symfony\Component\Mailer\Bridge\Mailchimp\Transport;
 
 use Psr\Log\LoggerInterface;
+use Symfony\Component\Mailer\Envelope;
 use Symfony\Component\Mailer\Exception\HttpTransportException;
 use Symfony\Component\Mailer\SentMessage;
 use Symfony\Component\Mailer\Transport\AbstractHttpTransport;
+use Symfony\Component\Mime\Address;
 use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
 use Symfony\Contracts\HttpClient\HttpClientInterface;
 use Symfony\Contracts\HttpClient\ResponseInterface;
@@ -45,7 +47,9 @@ protected function doSendHttp(SentMessage $message): ResponseInterface
         $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/1.0/messages/send-raw.json', [
             'json' => [
                 'key' => $this->key,
-                'to' => $this->stringifyAddresses($envelope->getRecipients()),
+                'to' => array_map(function (Address $recipient): string {
+                    return $recipient->getAddress();
+                }, $envelope->getRecipients()),
                 'from_email' => $envelope->getSender()->toString(),
                 'raw_message' => $message->toString(),
             ],
diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php
index 659062d947f16..7ec8313612049 100644
--- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php
+++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php
@@ -12,8 +12,12 @@
 namespace Symfony\Component\Mailer\Tests\Transport\Smtp;
 
 use PHPUnit\Framework\TestCase;
+use Symfony\Component\Mailer\Envelope;
 use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport;
+use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream;
 use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\RawMessage;
 
 class SmtpTransportTest extends TestCase
 {
@@ -25,4 +29,95 @@ public function testToString()
         $t = new SmtpTransport((new SocketStream())->setHost('127.0.0.1')->setPort(2525)->disableTls());
         $this->assertEquals('smtp://127.0.0.1:2525', (string) $t);
     }
+
+    public function testSendDoesNotPingBelowThreshold(): void
+    {
+        $stream = new DummyStream();
+        $envelope = new Envelope(new Address('sender@example.org'), [new Address('recipient@example.org')]);
+
+        $transport = new SmtpTransport($stream);
+        $transport->send(new RawMessage('Message 1'), $envelope);
+        $transport->send(new RawMessage('Message 2'), $envelope);
+        $transport->send(new RawMessage('Message 3'), $envelope);
+
+        $this->assertNotContains("NOOP\r\n", $stream->getCommands());
+    }
+
+    public function testSendDoesPingAboveThreshold(): void
+    {
+        $stream = new DummyStream();
+        $envelope = new Envelope(new Address('sender@example.org'), [new Address('recipient@example.org')]);
+
+        $transport = new SmtpTransport($stream);
+        $transport->setPingThreshold(1);
+
+        $transport->send(new RawMessage('Message 1'), $envelope);
+        $transport->send(new RawMessage('Message 2'), $envelope);
+
+        $this->assertNotContains("NOOP\r\n", $stream->getCommands());
+
+        $stream->clearCommands();
+        sleep(1);
+
+        $transport->send(new RawMessage('Message 3'), $envelope);
+        $this->assertContains("NOOP\r\n", $stream->getCommands());
+    }
+}
+
+class DummyStream extends AbstractStream
+{
+    /**
+     * @var string
+     */
+    private $nextResponse;
+
+    /**
+     * @var string[]
+     */
+    private $commands;
+
+    public function initialize(): void
+    {
+        $this->nextResponse = '220 localhost';
+    }
+
+    public function write(string $bytes, $debug = true): void
+    {
+        $this->commands[] = $bytes;
+
+        if (0 === strpos($bytes, 'DATA')) {
+            $this->nextResponse = '354 Enter message, ending with "." on a line by itself';
+        } elseif (0 === strpos($bytes, 'QUIT')) {
+            $this->nextResponse = '221 Goodbye';
+        } else {
+            $this->nextResponse = '250 OK';
+        }
+    }
+
+    public function readLine(): string
+    {
+        return $this->nextResponse;
+    }
+
+    public function flush(): void
+    {
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getCommands(): array
+    {
+        return $this->commands;
+    }
+
+    public function clearCommands(): void
+    {
+        $this->commands = [];
+    }
+
+    protected function getReadConnectionDescription(): string
+    {
+        return 'null';
+    }
 }
diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php
index cb3b4e0ae54c1..091b5e2bc5a60 100644
--- a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php
+++ b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php
@@ -35,6 +35,8 @@ class SmtpTransport extends AbstractTransport
     private $restartThreshold = 100;
     private $restartThresholdSleep = 0;
     private $restartCounter;
+    private $pingThreshold = 100;
+    private $lastMessageTime = 0;
     private $stream;
     private $domain = '[127.0.0.1]';
 
@@ -66,6 +68,28 @@ public function setRestartThreshold(int $threshold, int $sleep = 0): self
         return $this;
     }
 
+    /**
+     * Sets the minimum number of seconds required between two messages, before the server is pinged.
+     * If the transport wants to send a message and the time since the last message exceeds the specified threshold,
+     * the transport will ping the server first (NOOP command) to check if the connection is still alive.
+     * Otherwise the message will be sent without pinging the server first.
+     *
+     * Do not set the threshold too low, as the SMTP server may drop the connection if there are too many
+     * non-mail commands (like pinging the server with NOOP).
+     *
+     * By default, the threshold is set to 100 seconds.
+     *
+     * @param int $seconds The minimum number of seconds between two messages required to ping the server
+     *
+     * @return $this
+     */
+    public function setPingThreshold(int $seconds): self
+    {
+        $this->pingThreshold = $seconds;
+
+        return $this;
+    }
+
     /**
      * Sets the name of the local domain that will be used in HELO.
      *
@@ -160,7 +184,10 @@ public function executeCommand(string $command, array $codes): string
 
     protected function doSend(SentMessage $message): void
     {
-        $this->ping();
+        if (microtime(true) - $this->lastMessageTime > $this->pingThreshold) {
+            $this->ping();
+        }
+
         if (!$this->started) {
             $this->start();
         }
@@ -183,6 +210,8 @@ protected function doSend(SentMessage $message): void
             $e->appendDebug($this->stream->getDebug());
 
             throw $e;
+        } finally {
+            $this->lastMessageTime = microtime(true);
         }
     }
 
@@ -213,6 +242,7 @@ private function start(): void
         $this->assertResponseCode($this->getFullResponse(), [220]);
         $this->doHeloCommand();
         $this->started = true;
+        $this->lastMessageTime = 0;
 
         $this->getLogger()->debug(sprintf('Email transport "%s" started', __CLASS__));
     }
diff --git a/src/Symfony/Component/Messenger/HandleTrait.php b/src/Symfony/Component/Messenger/HandleTrait.php
index 27c7d84d217c4..7ba5deb7c0259 100644
--- a/src/Symfony/Component/Messenger/HandleTrait.php
+++ b/src/Symfony/Component/Messenger/HandleTrait.php
@@ -37,7 +37,7 @@ trait HandleTrait
     private function handle($message)
     {
         if (!$this->messageBus instanceof MessageBusInterface) {
-            throw new LogicException(sprintf('You must provide a "%s" instance in the "%s::$messageBus" property, "%s" given.', MessageBusInterface::class, \get_class($this), \is_object($this->messageBus) ? \get_class($this->messageBus) : \gettype($this->messageBus)));
+            throw new LogicException(sprintf('You must provide a "%s" instance in the "%s::$messageBus" property, "%s" given.', MessageBusInterface::class, static::class, \is_object($this->messageBus) ? \get_class($this->messageBus) : \gettype($this->messageBus)));
         }
 
         $envelope = $this->messageBus->dispatch($message);
@@ -45,7 +45,7 @@ private function handle($message)
         $handledStamps = $envelope->all(HandledStamp::class);
 
         if (!$handledStamps) {
-            throw new LogicException(sprintf('Message of type "%s" was handled zero times. Exactly one handler is expected when using "%s::%s()".', \get_class($envelope->getMessage()), \get_class($this), __FUNCTION__));
+            throw new LogicException(sprintf('Message of type "%s" was handled zero times. Exactly one handler is expected when using "%s::%s()".', \get_class($envelope->getMessage()), static::class, __FUNCTION__));
         }
 
         if (\count($handledStamps) > 1) {
@@ -53,7 +53,7 @@ private function handle($message)
                 return sprintf('"%s"', $stamp->getHandlerName());
             }, $handledStamps));
 
-            throw new LogicException(sprintf('Message of type "%s" was handled multiple times. Only one handler is expected when using "%s::%s()", got %d: %s.', \get_class($envelope->getMessage()), \get_class($this), __FUNCTION__, \count($handledStamps), $handlers));
+            throw new LogicException(sprintf('Message of type "%s" was handled multiple times. Only one handler is expected when using "%s::%s()", got %d: %s.', \get_class($envelope->getMessage()), static::class, __FUNCTION__, \count($handledStamps), $handlers));
         }
 
         return $handledStamps[0]->getResult();
diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php
index 2b4017e946ce5..b700ca2f334d3 100644
--- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php
+++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php
@@ -20,6 +20,7 @@
 use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer;
 use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
 use Doctrine\DBAL\Types\Type;
+use Doctrine\DBAL\Types\Types;
 use Symfony\Component\Messenger\Exception\InvalidArgumentException;
 use Symfony\Component\Messenger\Exception\TransportException;
 
@@ -53,12 +54,18 @@ class Connection
     private $schemaSynchronizer;
     private $autoSetup;
 
+    private static $useDeprecatedConstants;
+
     public function __construct(array $configuration, DBALConnection $driverConnection, SchemaSynchronizer $schemaSynchronizer = null)
     {
         $this->configuration = array_replace_recursive(self::DEFAULT_OPTIONS, $configuration);
         $this->driverConnection = $driverConnection;
         $this->schemaSynchronizer = $schemaSynchronizer ?? new SingleDatabaseSynchronizer($this->driverConnection);
         $this->autoSetup = $this->configuration['auto_setup'];
+
+        if (null === self::$useDeprecatedConstants) {
+            self::$useDeprecatedConstants = !class_exists(Types::class);
+        }
     }
 
     public function getConfiguration(): array
@@ -125,12 +132,18 @@ public function send(string $body, array $headers, int $delay = 0): string
             $this->configuration['queue_name'],
             $now,
             $availableAt,
-        ], [
+        ], self::$useDeprecatedConstants ? [
             null,
             null,
             null,
             Type::DATETIME,
             Type::DATETIME,
+        ] : [
+            null,
+            null,
+            null,
+            Types::DATETIME_MUTABLE,
+            Types::DATETIME_MUTABLE,
         ]);
 
         return $this->driverConnection->lastInsertId();
@@ -169,7 +182,7 @@ public function get(): ?array
                 $now,
                 $doctrineEnvelope['id'],
             ], [
-                Type::DATETIME,
+                self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE,
             ]);
 
             $this->driverConnection->commit();
@@ -278,9 +291,12 @@ private function createAvailableMessagesQueryBuilder(): QueryBuilder
                 $redeliverLimit,
                 $now,
                 $this->configuration['queue_name'],
-            ], [
+            ], self::$useDeprecatedConstants ? [
                 Type::DATETIME,
                 Type::DATETIME,
+            ] : [
+                Types::DATETIME_MUTABLE,
+                Types::DATETIME_MUTABLE,
             ]);
     }
 
@@ -314,20 +330,20 @@ private function getSchema(): Schema
     {
         $schema = new Schema([], [], $this->driverConnection->getSchemaManager()->createSchemaConfig());
         $table = $schema->createTable($this->configuration['table_name']);
-        $table->addColumn('id', Type::BIGINT)
+        $table->addColumn('id', self::$useDeprecatedConstants ? Type::BIGINT : Types::BIGINT)
             ->setAutoincrement(true)
             ->setNotnull(true);
-        $table->addColumn('body', Type::TEXT)
+        $table->addColumn('body', self::$useDeprecatedConstants ? Type::TEXT : Types::TEXT)
             ->setNotnull(true);
-        $table->addColumn('headers', Type::TEXT)
+        $table->addColumn('headers', self::$useDeprecatedConstants ? Type::TEXT : Types::TEXT)
             ->setNotnull(true);
-        $table->addColumn('queue_name', Type::STRING)
+        $table->addColumn('queue_name', self::$useDeprecatedConstants ? Type::STRING : Types::STRING)
             ->setNotnull(true);
-        $table->addColumn('created_at', Type::DATETIME)
+        $table->addColumn('created_at', self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE)
             ->setNotnull(true);
-        $table->addColumn('available_at', Type::DATETIME)
+        $table->addColumn('available_at', self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE)
             ->setNotnull(true);
-        $table->addColumn('delivered_at', Type::DATETIME)
+        $table->addColumn('delivered_at', self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE)
             ->setNotnull(false);
         $table->setPrimaryKey(['id']);
         $table->addIndex(['queue_name']);
diff --git a/src/Symfony/Component/Mime/Encoder/Base64ContentEncoder.php b/src/Symfony/Component/Mime/Encoder/Base64ContentEncoder.php
index 338490b3e5909..cb7f911678863 100644
--- a/src/Symfony/Component/Mime/Encoder/Base64ContentEncoder.php
+++ b/src/Symfony/Component/Mime/Encoder/Base64ContentEncoder.php
@@ -24,7 +24,7 @@ public function encodeByteStream($stream, int $maxLineLength = 0): iterable
             throw new \TypeError(sprintf('Method "%s" takes a stream as a first argument.', __METHOD__));
         }
 
-        $filter = stream_filter_append($stream, 'convert.base64-encode', \STREAM_FILTER_READ, [
+        $filter = stream_filter_append($stream, 'convert.base64-encode', STREAM_FILTER_READ, [
             'line-length' => 0 >= $maxLineLength || 76 < $maxLineLength ? 76 : $maxLineLength,
             'line-break-chars' => "\r\n",
         ]);
diff --git a/src/Symfony/Component/Mime/Encoder/QpEncoder.php b/src/Symfony/Component/Mime/Encoder/QpEncoder.php
index ff9b0cc12e08c..4f249e069ee95 100644
--- a/src/Symfony/Component/Mime/Encoder/QpEncoder.php
+++ b/src/Symfony/Component/Mime/Encoder/QpEncoder.php
@@ -89,7 +89,7 @@ class QpEncoder implements EncoderInterface
 
     public function __construct()
     {
-        $id = \get_class($this);
+        $id = static::class;
         if (!isset(self::$safeMapShare[$id])) {
             $this->initSafeMap();
             self::$safeMapShare[$id] = $this->safeMap;
diff --git a/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php b/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php
index b219f28b9d340..c0adbe3a0c0ce 100644
--- a/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php
+++ b/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php
@@ -12,6 +12,7 @@
 namespace Symfony\Component\Mime\Test\Constraint;
 
 use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\Mime\Message;
 use Symfony\Component\Mime\RawMessage;
 
 final class EmailAttachmentCount extends Constraint
diff --git a/src/Symfony/Component/Mime/Test/Constraint/EmailHtmlBodyContains.php b/src/Symfony/Component/Mime/Test/Constraint/EmailHtmlBodyContains.php
index 8965195144a0b..3c61376e1acb1 100644
--- a/src/Symfony/Component/Mime/Test/Constraint/EmailHtmlBodyContains.php
+++ b/src/Symfony/Component/Mime/Test/Constraint/EmailHtmlBodyContains.php
@@ -12,6 +12,8 @@
 namespace Symfony\Component\Mime\Test\Constraint;
 
 use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\Mime\Message;
+use Symfony\Component\Mime\RawMessage;
 
 final class EmailHtmlBodyContains extends Constraint
 {
diff --git a/src/Symfony/Component/Mime/Test/Constraint/EmailTextBodyContains.php b/src/Symfony/Component/Mime/Test/Constraint/EmailTextBodyContains.php
index b5e87f96f5f92..063d96306ba6b 100644
--- a/src/Symfony/Component/Mime/Test/Constraint/EmailTextBodyContains.php
+++ b/src/Symfony/Component/Mime/Test/Constraint/EmailTextBodyContains.php
@@ -12,6 +12,8 @@
 namespace Symfony\Component\Mime\Test\Constraint;
 
 use PHPUnit\Framework\Constraint\Constraint;
+use Symfony\Component\Mime\Message;
+use Symfony\Component\Mime\RawMessage;
 
 final class EmailTextBodyContains extends Constraint
 {
diff --git a/src/Symfony/Component/Notifier/Channel/BrowserChannel.php b/src/Symfony/Component/Notifier/Channel/BrowserChannel.php
index bd6bb29660740..c0f6dbc7bf346 100644
--- a/src/Symfony/Component/Notifier/Channel/BrowserChannel.php
+++ b/src/Symfony/Component/Notifier/Channel/BrowserChannel.php
@@ -12,7 +12,6 @@
 namespace Symfony\Component\Notifier\Channel;
 
 use Symfony\Component\HttpFoundation\RequestStack;
-use Symfony\Component\Messenger\MessageBusInterface;
 use Symfony\Component\Notifier\Notification\Notification;
 use Symfony\Component\Notifier\Recipient\Recipient;
 
@@ -30,7 +29,7 @@ public function __construct(RequestStack $stack)
         $this->stack = $stack;
     }
 
-    public function notify(Notification $notification, Recipient $recipient, string $transportName = null, MessageBusInterface $bus = null): void
+    public function notify(Notification $notification, Recipient $recipient, string $transportName = null): void
     {
         if (null === $request = $this->stack->getCurrentRequest()) {
             return;
diff --git a/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php b/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php
index 2b0cae4b20c11..98e97be5f8bb8 100644
--- a/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php
+++ b/src/Symfony/Component/Notifier/Transport/AbstractTransportFactory.php
@@ -18,7 +18,6 @@
 
 /**
  * @author Konstantin Myakshin 
- *
  * @author Fabien Potencier 
  *
  * @experimental in 5.0
diff --git a/src/Symfony/Component/Notifier/Transport/NullTransport.php b/src/Symfony/Component/Notifier/Transport/NullTransport.php
index 658243ae7d539..4973fbba2cddd 100644
--- a/src/Symfony/Component/Notifier/Transport/NullTransport.php
+++ b/src/Symfony/Component/Notifier/Transport/NullTransport.php
@@ -11,7 +11,11 @@
 
 namespace Symfony\Component\Notifier\Transport;
 
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
+use Symfony\Component\Notifier\Event\MessageEvent;
 use Symfony\Component\Notifier\Message\MessageInterface;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
 
 /**
  * @author Fabien Potencier 
@@ -20,8 +24,18 @@
  */
 class NullTransport implements TransportInterface
 {
+    private $dispatcher;
+
+    public function __construct(EventDispatcherInterface $dispatcher = null)
+    {
+        $this->dispatcher = class_exists(Event::class) ? LegacyEventDispatcherProxy::decorate($dispatcher) : $dispatcher;
+    }
+
     public function send(MessageInterface $message): void
     {
+        if (null !== $this->dispatcher) {
+            $this->dispatcher->dispatch(new MessageEvent($message));
+        }
     }
 
     public function __toString(): string
diff --git a/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php b/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php
index abfcd1c75d3f7..196d052a05163 100644
--- a/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php
+++ b/src/Symfony/Component/Notifier/Transport/NullTransportFactory.php
@@ -26,7 +26,7 @@ final class NullTransportFactory extends AbstractTransportFactory
     public function create(Dsn $dsn): TransportInterface
     {
         if ('null' === $dsn->getScheme()) {
-            return new NullTransport();
+            return new NullTransport($this->dispatcher);
         }
 
         throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes());
diff --git a/src/Symfony/Component/Process/PhpProcess.php b/src/Symfony/Component/Process/PhpProcess.php
index 63f9cafffeca0..2bc338e5e2313 100644
--- a/src/Symfony/Component/Process/PhpProcess.php
+++ b/src/Symfony/Component/Process/PhpProcess.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Process;
 
+use Symfony\Component\Process\Exception\LogicException;
 use Symfony\Component\Process\Exception\RuntimeException;
 
 /**
@@ -49,6 +50,14 @@ public function __construct(string $script, string $cwd = null, array $env = nul
         parent::__construct($php, $cwd, $env, $script, $timeout);
     }
 
+    /**
+     * {@inheritdoc}
+     */
+    public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+    {
+        throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
+    }
+
     /**
      * {@inheritdoc}
      */
diff --git a/src/Symfony/Component/Process/Tests/PhpProcessTest.php b/src/Symfony/Component/Process/Tests/PhpProcessTest.php
index b7b21ebcb160f..5f1e5e6700622 100644
--- a/src/Symfony/Component/Process/Tests/PhpProcessTest.php
+++ b/src/Symfony/Component/Process/Tests/PhpProcessTest.php
@@ -12,6 +12,7 @@
 namespace Symfony\Component\Process\Tests;
 
 use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\Exception\LogicException;
 use Symfony\Component\Process\PhpExecutableFinder;
 use Symfony\Component\Process\PhpProcess;
 
@@ -60,4 +61,14 @@ public function testPassingPhpExplicitly()
         $process->run();
         $this->assertEquals($expected, $process->getOutput());
     }
+
+    public function testProcessCannotBeCreatedUsingFromShellCommandLine()
+    {
+        static::expectException(LogicException::class);
+        static::expectExceptionMessage('The "Symfony\Component\Process\PhpProcess::fromShellCommandline()" method cannot be called when using "Symfony\Component\Process\PhpProcess".');
+        PhpProcess::fromShellCommandline(<< 0) {
         $written = fwrite(STDOUT, (string) $out, 32768);
         if (false === $written) {
-            die(ERR_WRITE_FAILED);
+            exit(ERR_WRITE_FAILED);
         }
         $out = (string) substr($out, $written);
     }
@@ -48,7 +48,7 @@
     if (in_array(STDERR, $w) && strlen($err) > 0) {
         $written = fwrite(STDERR, (string) $err, 32768);
         if (false === $written) {
-            die(ERR_WRITE_FAILED);
+            exit(ERR_WRITE_FAILED);
         }
         $err = (string) substr($err, $written);
     }
@@ -65,7 +65,7 @@
         if (false === $str || feof(STDIN)) {
             $read = null;
             if (!feof(STDIN)) {
-                die(ERR_READ_FAILED);
+                exit(ERR_READ_FAILED);
             }
         }
     }
diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php
index 4d5f618115591..8183b6fc55e97 100644
--- a/src/Symfony/Component/Routing/Annotation/Route.php
+++ b/src/Symfony/Component/Routing/Annotation/Route.php
@@ -43,7 +43,7 @@ class Route
     public function __construct(array $data)
     {
         if (isset($data['localized_paths'])) {
-            throw new \BadMethodCallException(sprintf('Unknown property "localized_paths" on annotation "%s".', \get_class($this)));
+            throw new \BadMethodCallException(sprintf('Unknown property "localized_paths" on annotation "%s".', static::class));
         }
 
         if (isset($data['value'])) {
@@ -74,7 +74,7 @@ public function __construct(array $data)
         foreach ($data as $key => $value) {
             $method = 'set'.str_replace('_', '', $key);
             if (!method_exists($this, $method)) {
-                throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, \get_class($this)));
+                throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, static::class));
             }
             $this->$method($value);
         }
diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php
index 0c52b121f800e..c5d770aa8cee3 100644
--- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php
+++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php
@@ -18,6 +18,7 @@
 use Symfony\Component\Routing\Annotation\Route as RouteAnnotation;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RouteCompiler;
 
 /**
  * AnnotationClassLoader loads routing information from a PHP class and its methods.
@@ -207,6 +208,7 @@ protected function addRoute(RouteCollection $collection, $annot, array $globals,
             $this->configureRoute($route, $class, $method, $annot);
             if (0 !== $locale) {
                 $route->setDefault('_locale', $locale);
+                $route->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER));
                 $route->setDefault('_canonical_route', $name);
                 $collection->add($name.'.'.$locale, $route);
             } else {
diff --git a/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php b/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php
index 085fde4bc9f4c..84899aa2e27fc 100644
--- a/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php
+++ b/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php
@@ -15,6 +15,7 @@
 use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RouteCompiler;
 
 trait AddTrait
 {
@@ -67,6 +68,7 @@ final public function add(string $name, $path): RouteConfigurator
             $routes->add($name.'.'.$locale, $route = $this->createRoute($path));
             $this->collection->add($this->name.$name.'.'.$locale, $route);
             $route->setDefault('_locale', $locale);
+            $route->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER));
             $route->setDefault('_canonical_route', $this->name.$name);
         }
 
diff --git a/src/Symfony/Component/Routing/Loader/ObjectLoader.php b/src/Symfony/Component/Routing/Loader/ObjectLoader.php
index aefb8295115ab..39e7d230efb9a 100644
--- a/src/Symfony/Component/Routing/Loader/ObjectLoader.php
+++ b/src/Symfony/Component/Routing/Loader/ObjectLoader.php
@@ -52,7 +52,7 @@ public function load($resource, string $type = null)
         $loaderObject = $this->getObject($parts[0]);
 
         if (!\is_object($loaderObject)) {
-            throw new \TypeError(sprintf('%s:getObject() must return an object: %s returned', \get_class($this), \gettype($loaderObject)));
+            throw new \TypeError(sprintf('%s:getObject() must return an object: %s returned', static::class, \gettype($loaderObject)));
         }
 
         if (!\is_callable([$loaderObject, $method])) {
diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php
index 9d46cfd438e61..7dbfca881daee 100644
--- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php
@@ -16,6 +16,7 @@
 use Symfony\Component\Config\Util\XmlUtils;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RouteCompiler;
 
 /**
  * XmlFileLoader loads XML routing files.
@@ -129,6 +130,7 @@ protected function parseRoute(RouteCollection $collection, \DOMElement $node, st
             foreach ($paths as $locale => $p) {
                 $defaults['_locale'] = $locale;
                 $defaults['_canonical_route'] = $id;
+                $requirements['_locale'] = preg_quote($locale, RouteCompiler::REGEX_DELIMITER);
                 $route = new Route($p, $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition);
                 $collection->add($id.'.'.$locale, $route);
             }
diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php
index 3b47b20f4a4a2..ae1c23edbfd62 100644
--- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php
+++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php
@@ -15,6 +15,7 @@
 use Symfony\Component\Config\Resource\FileResource;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RouteCompiler;
 use Symfony\Component\Yaml\Exception\ParseException;
 use Symfony\Component\Yaml\Parser as YamlParser;
 use Symfony\Component\Yaml\Yaml;
@@ -140,6 +141,7 @@ protected function parseRoute(RouteCollection $collection, string $name, array $
             foreach ($config['path'] as $locale => $path) {
                 $localizedRoute = clone $route;
                 $localizedRoute->setDefault('_locale', $locale);
+                $localizedRoute->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER));
                 $localizedRoute->setDefault('_canonical_route', $name);
                 $localizedRoute->setPath($path);
                 $collection->add($name.'.'.$locale, $localizedRoute);
diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php
index 59f3a327e0f6a..1f94a46cbd47f 100644
--- a/src/Symfony/Component/Routing/RouteCompiler.php
+++ b/src/Symfony/Component/Routing/RouteCompiler.php
@@ -61,6 +61,14 @@ public static function compile(Route $route)
             $hostRegex = $result['regex'];
         }
 
+        $locale = $route->getDefault('_locale');
+        if (null !== $locale && null !== $route->getDefault('_canonical_route') && preg_quote($locale, self::REGEX_DELIMITER) === $route->getRequirement('_locale')) {
+            $requirements = $route->getRequirements();
+            unset($requirements['_locale']);
+            $route->setRequirements($requirements);
+            $route->setPath(str_replace('{_locale}', $locale, $route->getPath()));
+        }
+
         $path = $route->getPath();
 
         $result = self::compilePattern($route, $path, false);
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/compiled_url_matcher14.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/compiled_url_matcher14.php
new file mode 100644
index 0000000000000..3645aff291846
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/compiled_url_matcher14.php
@@ -0,0 +1,19 @@
+ [[['_route' => 'home', '_locale' => 'fr'], null, null, null, false, false, null]],
+        '/en/home' => [[['_route' => 'home', '_locale' => 'en'], null, null, null, false, false, null]],
+    ],
+    [ // $regexpList
+    ],
+    [ // $dynamicRoutes
+    ],
+    null, // $checkCondition
+];
diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php
index 9dc9e2c50c2d8..42e256e120c21 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php
@@ -121,6 +121,9 @@ public function testLocalizedPathRoutes()
         $this->assertCount(2, $routes);
         $this->assertEquals('/path', $routes->get('action.en')->getPath());
         $this->assertEquals('/pad', $routes->get('action.nl')->getPath());
+
+        $this->assertEquals('nl', $routes->get('action.nl')->getRequirement('_locale'));
+        $this->assertEquals('en', $routes->get('action.en')->getRequirement('_locale'));
     }
 
     public function testLocalizedPathRoutesWithExplicitPathPropety()
diff --git a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php
index 71f9df15470a1..789848c66021a 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php
@@ -229,11 +229,11 @@ public function testRoutingI18nConfigurator()
 
         $expectedCollection = new RouteCollection();
 
-        $expectedCollection->add('foo.en', (new Route('/glish/foo'))->setDefaults(['_locale' => 'en', '_canonical_route' => 'foo']));
-        $expectedCollection->add('bar.en', (new Route('/glish/bar'))->setDefaults(['_locale' => 'en', '_canonical_route' => 'bar']));
-        $expectedCollection->add('baz.en', (new Route('/baz'))->setDefaults(['_locale' => 'en', '_canonical_route' => 'baz']));
-        $expectedCollection->add('c_foo.fr', (new Route('/ench/pub/foo'))->setDefaults(['_locale' => 'fr', '_canonical_route' => 'c_foo']));
-        $expectedCollection->add('c_bar.fr', (new Route('/ench/pub/bar'))->setDefaults(['_locale' => 'fr', '_canonical_route' => 'c_bar']));
+        $expectedCollection->add('foo.en', (new Route('/glish/foo'))->setDefaults(['_locale' => 'en', '_canonical_route' => 'foo'])->setRequirement('_locale', 'en'));
+        $expectedCollection->add('bar.en', (new Route('/glish/bar'))->setDefaults(['_locale' => 'en', '_canonical_route' => 'bar'])->setRequirement('_locale', 'en'));
+        $expectedCollection->add('baz.en', (new Route('/baz'))->setDefaults(['_locale' => 'en', '_canonical_route' => 'baz'])->setRequirement('_locale', 'en'));
+        $expectedCollection->add('c_foo.fr', (new Route('/ench/pub/foo'))->setDefaults(['_locale' => 'fr', '_canonical_route' => 'c_foo'])->setRequirement('_locale', 'fr'));
+        $expectedCollection->add('c_bar.fr', (new Route('/ench/pub/bar'))->setDefaults(['_locale' => 'fr', '_canonical_route' => 'c_bar'])->setRequirement('_locale', 'fr'));
 
         $expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub_i18n.php')));
         $expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_i18n.php')));
diff --git a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
index e27149f93125c..66d54fc985c4c 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
@@ -185,6 +185,9 @@ public function testLocalizedImports()
 
         $this->assertEquals('/le-prefix/le-suffix', $routeCollection->get('imported.fr')->getPath());
         $this->assertEquals('/the-prefix/suffix', $routeCollection->get('imported.en')->getPath());
+
+        $this->assertEquals('fr', $routeCollection->get('imported.fr')->getRequirement('_locale'));
+        $this->assertEquals('en', $routeCollection->get('imported.en')->getRequirement('_locale'));
     }
 
     public function testLocalizedImportsOfNotLocalizedRoutes()
diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
index b3f689a3d2569..301908d88d1bc 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
@@ -321,6 +321,9 @@ public function testImportingRoutesWithLocales()
         $this->assertCount(2, $routes);
         $this->assertEquals('/nl/voorbeeld', $routes->get('imported.nl')->getPath());
         $this->assertEquals('/en/example', $routes->get('imported.en')->getPath());
+
+        $this->assertEquals('nl', $routes->get('imported.nl')->getRequirement('_locale'));
+        $this->assertEquals('en', $routes->get('imported.en')->getRequirement('_locale'));
     }
 
     public function testImportingNonLocalizedRoutesWithLocales()
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php
index c92a9cfa52737..72d26933efb47 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php
@@ -12,6 +12,9 @@
 namespace Symfony\Component\Routing\Tests\Matcher\Dumper;
 
 use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+use Symfony\Component\Routing\Loader\PhpFileLoader;
 use Symfony\Component\Routing\Matcher\CompiledUrlMatcher;
 use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper;
 use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
@@ -442,21 +445,34 @@ public function getRouteCollections()
         $hostCollection->add('r1', (new Route('abc{foo}'))->setHost('{foo}.exampple.com'));
         $hostCollection->add('r2', (new Route('abc{foo}'))->setHost('{foo}.exampple.com'));
 
+        /* test case 14 */
+        $fixedLocaleCollection = new RouteCollection();
+        $routes = new RoutingConfigurator($fixedLocaleCollection, new PhpFileLoader(new FileLocator()), __FILE__, __FILE__);
+        $routes
+            ->collection()
+            ->prefix('/{_locale}')
+            ->add('home', [
+                'fr' => 'accueil',
+                'en' => 'home',
+            ])
+        ;
+
         return [
-           [new RouteCollection(), 'compiled_url_matcher0.php'],
-           [$collection, 'compiled_url_matcher1.php'],
-           [$redirectCollection, 'compiled_url_matcher2.php'],
-           [$rootprefixCollection, 'compiled_url_matcher3.php'],
-           [$headMatchCasesCollection, 'compiled_url_matcher4.php'],
-           [$groupOptimisedCollection, 'compiled_url_matcher5.php'],
-           [$trailingSlashCollection, 'compiled_url_matcher6.php'],
-           [$trailingSlashCollection, 'compiled_url_matcher7.php'],
-           [$unicodeCollection, 'compiled_url_matcher8.php'],
-           [$hostTreeCollection, 'compiled_url_matcher9.php'],
-           [$chunkedCollection, 'compiled_url_matcher10.php'],
-           [$demoCollection, 'compiled_url_matcher11.php'],
-           [$suffixCollection, 'compiled_url_matcher12.php'],
-           [$hostCollection, 'compiled_url_matcher13.php'],
+            [new RouteCollection(), 'compiled_url_matcher0.php'],
+            [$collection, 'compiled_url_matcher1.php'],
+            [$redirectCollection, 'compiled_url_matcher2.php'],
+            [$rootprefixCollection, 'compiled_url_matcher3.php'],
+            [$headMatchCasesCollection, 'compiled_url_matcher4.php'],
+            [$groupOptimisedCollection, 'compiled_url_matcher5.php'],
+            [$trailingSlashCollection, 'compiled_url_matcher6.php'],
+            [$trailingSlashCollection, 'compiled_url_matcher7.php'],
+            [$unicodeCollection, 'compiled_url_matcher8.php'],
+            [$hostTreeCollection, 'compiled_url_matcher9.php'],
+            [$chunkedCollection, 'compiled_url_matcher10.php'],
+            [$demoCollection, 'compiled_url_matcher11.php'],
+            [$suffixCollection, 'compiled_url_matcher12.php'],
+            [$hostCollection, 'compiled_url_matcher13.php'],
+            [$fixedLocaleCollection, 'compiled_url_matcher14.php'],
         ];
     }
 
diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/RememberMeAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/RememberMeAuthenticationProvider.php
index af0ec53c05cc1..670390e446c1e 100644
--- a/src/Symfony/Component/Security/Core/Authentication/Provider/RememberMeAuthenticationProvider.php
+++ b/src/Symfony/Component/Security/Core/Authentication/Provider/RememberMeAuthenticationProvider.php
@@ -15,7 +15,9 @@
 use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
 use Symfony\Component\Security\Core\Exception\AuthenticationException;
 use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\Security\Core\Exception\LogicException;
 use Symfony\Component\Security\Core\User\UserCheckerInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
 
 class RememberMeAuthenticationProvider implements AuthenticationProviderInterface
 {
@@ -48,6 +50,11 @@ public function authenticate(TokenInterface $token)
         }
 
         $user = $token->getUser();
+
+        if (!$token->getUser() instanceof UserInterface) {
+            throw new LogicException(sprintf('Method "%s::getUser()" must return a "%s" instance, "%s" returned.', \get_class($token), UserInterface::class, \is_object($user) ? \get_class($user) : \gettype($user)));
+        }
+
         $this->userChecker->checkPreAuth($user);
         $this->userChecker->checkPostAuth($user);
 
diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php
index 167e702192d25..9106334b99e8b 100644
--- a/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php
+++ b/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php
@@ -225,7 +225,7 @@ public function setAttribute(string $name, $value)
      */
     public function __toString()
     {
-        $class = \get_class($this);
+        $class = static::class;
         $class = substr($class, strrpos($class, '\\') + 1);
 
         $roles = [];
diff --git a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php
index 2246e9b4b0577..b2e76c5b46f12 100644
--- a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php
+++ b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php
@@ -33,8 +33,8 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
     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);
+        $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.');
@@ -76,6 +76,10 @@ public function encodePassword(string $raw, ?string $salt): string
      */
     public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
     {
+        if ('' === $raw) {
+            return false;
+        }
+
         if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) {
             return false;
         }
@@ -85,7 +89,7 @@ public function isPasswordValid(string $encoded, string $raw, ?string $salt): bo
             return (72 >= \strlen($raw) || 0 !== strpos($encoded, '$2')) && password_verify($raw, $encoded);
         }
 
-        if (\extension_loaded('sodium') && version_compare(\SODIUM_LIBRARY_VERSION, '1.0.14', '>=')) {
+        if (\extension_loaded('sodium') && version_compare(SODIUM_LIBRARY_VERSION, '1.0.14', '>=')) {
             return sodium_crypto_pwhash_str_verify($encoded, $raw);
         }
 
diff --git a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php
index d261d1c4b71f2..d6b7c0ba4de2c 100644
--- a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php
+++ b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php
@@ -34,8 +34,8 @@ public function __construct(int $opsLimit = null, int $memLimit = null)
             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 * 2014);
+        $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 * 2014);
 
         if (3 > $this->opsLimit) {
             throw new \InvalidArgumentException('$opsLimit must be 3 or greater.');
@@ -48,7 +48,7 @@ public function __construct(int $opsLimit = null, int $memLimit = null)
 
     public static function isSupported(): bool
     {
-        return version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : phpversion('libsodium'), '1.0.14', '>=');
+        return version_compare(\extension_loaded('sodium') ? SODIUM_LIBRARY_VERSION : phpversion('libsodium'), '1.0.14', '>=');
     }
 
     /**
@@ -76,6 +76,10 @@ public function encodePassword(string $raw, ?string $salt): string
      */
     public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
     {
+        if ('' === $raw) {
+            return false;
+        }
+
         if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) {
             return false;
         }
diff --git a/src/Symfony/Component/Security/Core/Exception/AuthenticationExpiredException.php b/src/Symfony/Component/Security/Core/Exception/AuthenticationExpiredException.php
index 7bc174f05ce82..b45c948acd353 100644
--- a/src/Symfony/Component/Security/Core/Exception/AuthenticationExpiredException.php
+++ b/src/Symfony/Component/Security/Core/Exception/AuthenticationExpiredException.php
@@ -12,7 +12,7 @@
 namespace Symfony\Component\Security\Core\Exception;
 
 /**
- * AuthenticationServiceException is thrown when an authenticated token becomes un-authenticated between requests.
+ * AuthenticationExpiredException is thrown when an authenticated token becomes un-authenticated between requests.
  *
  * In practice, this is due to the User changing between requests (e.g. password changes),
  * causes the token to become un-authenticated.
diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php
index 15aecb9ba778d..3875522c4a7b4 100644
--- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php
+++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php
@@ -13,7 +13,9 @@
 
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Security\Core\Authentication\Provider\RememberMeAuthenticationProvider;
+use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
 use Symfony\Component\Security\Core\Exception\DisabledException;
+use Symfony\Component\Security\Core\User\User;
 
 class RememberMeAuthenticationProviderTest extends TestCase
 {
@@ -23,6 +25,7 @@ public function testSupports()
 
         $this->assertTrue($provider->supports($this->getSupportedToken()));
         $this->assertFalse($provider->supports($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock()));
+        $this->assertFalse($provider->supports($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\RememberMeToken')->disableOriginalConstructor()->getMock()));
     }
 
     public function testAuthenticateWhenTokenIsNotSupported()
@@ -44,6 +47,17 @@ public function testAuthenticateWhenSecretsDoNotMatch()
         $provider->authenticate($token);
     }
 
+    public function testAuthenticateThrowsOnNonUserInterfaceInstance()
+    {
+        $this->expectException('Symfony\Component\Security\Core\Exception\LogicException');
+        $this->expectExceptionMessage('Method "Symfony\Component\Security\Core\Authentication\Token\RememberMeToken::getUser()" must return a "Symfony\Component\Security\Core\User\UserInterface" instance, "string" returned.');
+
+        $provider = $this->getProvider();
+        $token = new RememberMeToken(new User('dummyuser', null), 'foo', 'test');
+        $token->setUser('stringish-user');
+        $provider->authenticate($token);
+    }
+
     public function testAuthenticateWhenPreChecksFails()
     {
         $this->expectException('Symfony\Component\Security\Core\Exception\DisabledException');
diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php
index 44cecafecd2ba..e77307d65dfc3 100644
--- a/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php
+++ b/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php
@@ -150,7 +150,7 @@ public function testMigrateFrom()
         $this->assertInstanceOf(MigratingPasswordEncoder::class, $encoder);
 
         $this->assertTrue($encoder->isPasswordValid((new SodiumPasswordEncoder())->encodePassword('foo', null), 'foo', null));
-        $this->assertTrue($encoder->isPasswordValid((new NativePasswordEncoder(null, null, null, \PASSWORD_BCRYPT))->encodePassword('foo', null), 'foo', null));
+        $this->assertTrue($encoder->isPasswordValid((new NativePasswordEncoder(null, null, null, PASSWORD_BCRYPT))->encodePassword('foo', null), 'foo', null));
         $this->assertTrue($encoder->isPasswordValid($digest->encodePassword('foo', null), 'foo', null));
         $this->assertStringStartsWith(SODIUM_CRYPTO_PWHASH_STRPREFIX, $encoder->encodePassword('foo', null));
     }
diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php
index 965bf3751c7ee..47b8ac09eaa69 100644
--- a/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php
+++ b/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php
@@ -53,6 +53,7 @@ public function testValidation()
         $result = $encoder->encodePassword('password', null);
         $this->assertTrue($encoder->isPasswordValid($result, 'password', null));
         $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null));
+        $this->assertFalse($encoder->isPasswordValid($result, '', null));
     }
 
     public function testNonArgonValidation()
diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php
index 8fa0813115e16..2c4527fef7cfb 100644
--- a/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php
+++ b/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php
@@ -29,6 +29,7 @@ public function testValidation()
         $result = $encoder->encodePassword('password', null);
         $this->assertTrue($encoder->isPasswordValid($result, 'password', null));
         $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null));
+        $this->assertFalse($encoder->isPasswordValid($result, '', null));
     }
 
     public function testBCryptValidation()
diff --git a/src/Symfony/Component/Security/Core/User/UserInterface.php b/src/Symfony/Component/Security/Core/User/UserInterface.php
index 71f481cfe5c93..c8c747ab1e9d7 100644
--- a/src/Symfony/Component/Security/Core/User/UserInterface.php
+++ b/src/Symfony/Component/Security/Core/User/UserInterface.php
@@ -19,10 +19,10 @@
  * password (for checking against a submitted password), assigning roles
  * and so on.
  *
- * Regardless of how your user are loaded or where they come from (a database,
- * configuration, web service, etc), you will have a class that implements
- * this interface. Objects that implement this interface are created and
- * loaded by different objects that implement UserProviderInterface
+ * Regardless of how your users are loaded or where they come from (a database,
+ * configuration, web service, etc.), you will have a class that implements
+ * this interface. Objects that implement this interface are created and 
+ * loaded by different objects that implement UserProviderInterface.
  *
  * @see UserProviderInterface
  *
diff --git a/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php b/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php
index 7dfbeff70e15b..36c1be3115855 100644
--- a/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php
+++ b/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php
@@ -113,7 +113,7 @@ public function createAuthenticatedToken(UserInterface $user, string $providerKe
      * Called when authentication executed, but failed (e.g. wrong username password).
      *
      * This should return the Response sent back to the user, like a
-     * RedirectResponse to the login page or a 403 response.
+     * RedirectResponse to the login page or a 401 response.
      *
      * If you return null, the request will continue, but the user will
      * not be authenticated. This is probably not what you want to do.
diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php
index 99704b3e758e4..ec2b1f10eab73 100644
--- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php
+++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php
@@ -86,7 +86,7 @@ public function authenticate(TokenInterface $token)
                 return $token;
             }
 
-            // this AccountStatusException causes the user to be logged out
+            // this causes the user to be logged out
             throw new AuthenticationExpiredException();
         }
 
diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php
index f6d4c8a587c0a..3da0279f4c8fe 100644
--- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php
+++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php
@@ -144,7 +144,8 @@ private function attemptSwitchUser(Request $request, string $username): ?TokenIn
                 return $token;
             }
 
-            throw new \LogicException(sprintf('You are already switched to "%s" user.', $token->getUsername()));
+            // User already switched, exit before seamlessly switching to another user
+            $token = $this->attemptExitUser($request);
         }
 
         $currentUsername = $token->getUsername();
diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
index 13ec17289ab5d..6eef7455793a7 100644
--- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
@@ -352,8 +352,9 @@ protected function runSessionOnKernelResponse($newToken, $original = null)
             $session->set('_security_session', $original);
         }
 
-        $tokenStorage = new UsageTrackingTokenStorage(new TokenStorage(), new class([
-            'session' => function () use ($session) { return $session; }
+        $tokenStorage = new UsageTrackingTokenStorage(new TokenStorage(), new class(['session' => function () use ($session) {
+            return $session;
+        },
         ]) implements ContainerInterface {
             use ServiceLocatorTrait;
         });
@@ -398,8 +399,9 @@ private function handleEventWithPreviousSession($userProviders, UserInterface $u
 
         $tokenStorage = new TokenStorage();
         $usageIndex = $session->getUsageIndex();
-        $tokenStorage = new UsageTrackingTokenStorage($tokenStorage, new class([
-            'session' => function () use ($session) { return $session; }
+        $tokenStorage = new UsageTrackingTokenStorage($tokenStorage, new class(['session' => function () use ($session) {
+            return $session;
+        },
         ]) implements ContainerInterface {
             use ServiceLocatorTrait;
         });
diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php
index b0ab43efd1643..04cc75e27bfd8 100644
--- a/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php
@@ -223,6 +223,39 @@ public function testSwitchUser()
         $this->assertInstanceOf('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $this->tokenStorage->getToken());
     }
 
+    public function testSwitchUserAlreadySwitched()
+    {
+        $originalToken = new UsernamePasswordToken('original', null, 'key', ['ROLE_FOO']);
+        $alreadySwitchedToken = new SwitchUserToken('switched_1', null, 'key', ['ROLE_BAR'], $originalToken);
+
+        $tokenStorage = new TokenStorage();
+        $tokenStorage->setToken($alreadySwitchedToken);
+
+        $targetUser = new User('kuba', 'password', ['ROLE_FOO', 'ROLE_BAR']);
+
+        $this->request->query->set('_switch_user', 'kuba');
+
+        $this->accessDecisionManager->expects($this->once())
+            ->method('decide')->with($originalToken, ['ROLE_ALLOWED_TO_SWITCH'], $targetUser)
+            ->willReturn(true);
+
+        $this->userProvider->expects($this->exactly(2))
+            ->method('loadUserByUsername')
+            ->withConsecutive(['kuba'])
+            ->will($this->onConsecutiveCalls($targetUser, $this->throwException(new UsernameNotFoundException())));
+        $this->userChecker->expects($this->once())
+            ->method('checkPostAuth')->with($targetUser);
+
+        $listener = new SwitchUserListener($tokenStorage, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager, null, '_switch_user', 'ROLE_ALLOWED_TO_SWITCH', null, false);
+        $listener($this->event);
+
+        $this->assertSame([], $this->request->query->all());
+        $this->assertSame('', $this->request->server->get('QUERY_STRING'));
+        $this->assertInstanceOf(SwitchUserToken::class, $tokenStorage->getToken());
+        $this->assertSame('kuba', $tokenStorage->getToken()->getUsername());
+        $this->assertSame($originalToken, $tokenStorage->getToken()->getOriginalToken());
+    }
+
     public function testSwitchUserWorksWithFalsyUsernames()
     {
         $token = new UsernamePasswordToken('username', '', 'key', ['ROLE_FOO']);
diff --git a/src/Symfony/Component/Serializer/Annotation/DiscriminatorMap.php b/src/Symfony/Component/Serializer/Annotation/DiscriminatorMap.php
index 1e0077c27a712..c1184a53a5f6a 100644
--- a/src/Symfony/Component/Serializer/Annotation/DiscriminatorMap.php
+++ b/src/Symfony/Component/Serializer/Annotation/DiscriminatorMap.php
@@ -39,11 +39,11 @@ class DiscriminatorMap
     public function __construct(array $data)
     {
         if (empty($data['typeProperty'])) {
-            throw new InvalidArgumentException(sprintf('Parameter "typeProperty" of annotation "%s" cannot be empty.', \get_class($this)));
+            throw new InvalidArgumentException(sprintf('Parameter "typeProperty" of annotation "%s" cannot be empty.', static::class));
         }
 
         if (empty($data['mapping'])) {
-            throw new InvalidArgumentException(sprintf('Parameter "mapping" of annotation "%s" cannot be empty.', \get_class($this)));
+            throw new InvalidArgumentException(sprintf('Parameter "mapping" of annotation "%s" cannot be empty.', static::class));
         }
 
         $this->typeProperty = $data['typeProperty'];
diff --git a/src/Symfony/Component/Serializer/Annotation/Groups.php b/src/Symfony/Component/Serializer/Annotation/Groups.php
index 7a9b0bd2c1052..4358a3e26dcb9 100644
--- a/src/Symfony/Component/Serializer/Annotation/Groups.php
+++ b/src/Symfony/Component/Serializer/Annotation/Groups.php
@@ -34,13 +34,13 @@ class Groups
     public function __construct(array $data)
     {
         if (!isset($data['value']) || !$data['value']) {
-            throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', \get_class($this)));
+            throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', static::class));
         }
 
         $value = (array) $data['value'];
         foreach ($value as $group) {
             if (!\is_string($group)) {
-                throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of strings.', \get_class($this)));
+                throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of strings.', static::class));
             }
         }
 
diff --git a/src/Symfony/Component/Serializer/Annotation/MaxDepth.php b/src/Symfony/Component/Serializer/Annotation/MaxDepth.php
index a274c20d85283..9939fdab1cd18 100644
--- a/src/Symfony/Component/Serializer/Annotation/MaxDepth.php
+++ b/src/Symfony/Component/Serializer/Annotation/MaxDepth.php
@@ -31,11 +31,11 @@ class MaxDepth
     public function __construct(array $data)
     {
         if (!isset($data['value'])) {
-            throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', \get_class($this)));
+            throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class));
         }
 
         if (!\is_int($data['value']) || $data['value'] <= 0) {
-            throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', \get_class($this)));
+            throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', static::class));
         }
 
         $this->maxDepth = $data['value'];
diff --git a/src/Symfony/Component/Serializer/Annotation/SerializedName.php b/src/Symfony/Component/Serializer/Annotation/SerializedName.php
index 2de4a2c42274d..747c8c55f108b 100644
--- a/src/Symfony/Component/Serializer/Annotation/SerializedName.php
+++ b/src/Symfony/Component/Serializer/Annotation/SerializedName.php
@@ -31,11 +31,11 @@ final class SerializedName
     public function __construct(array $data)
     {
         if (!isset($data['value'])) {
-            throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', \get_class($this)));
+            throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class));
         }
 
         if (!\is_string($data['value']) || empty($data['value'])) {
-            throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', \get_class($this)));
+            throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', static::class));
         }
 
         $this->serializedName = $data['value'];
diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
index 5620dc5c1e666..33942d73fe4ea 100644
--- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
+++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Serializer\Encoder;
 
+use Symfony\Component\Serializer\Exception\BadMethodCallException;
 use Symfony\Component\Serializer\Exception\NotEncodableValueException;
 use Symfony\Component\Serializer\SerializerAwareInterface;
 use Symfony\Component\Serializer\SerializerAwareTrait;
@@ -368,7 +369,7 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName =
         $removeEmptyTags = $this->context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false;
         $encoderIgnoredNodeTypes = $this->context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES];
 
-        if (\is_array($data) || ($data instanceof \Traversable && !$this->serializer->supportsNormalization($data, $this->format))) {
+        if (\is_array($data) || ($data instanceof \Traversable && (null === $this->serializer || !$this->serializer->supportsNormalization($data, $this->format)))) {
             foreach ($data as $key => $data) {
                 //Ah this is the magic @ attribute types.
                 if (0 === strpos($key, '@') && $this->isElementNameValid($attributeName = substr($key, 1))) {
@@ -407,6 +408,10 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName =
         }
 
         if (\is_object($data)) {
+            if (null === $this->serializer) {
+                throw new BadMethodCallException(sprintf('The serializer needs to be set to allow %s() to be used with object data.', __METHOD__));
+            }
+
             $data = $this->serializer->normalize($data, $this->format, $this->context);
             if (null !== $data && !is_scalar($data)) {
                 return $this->buildXml($parentNode, $data, $xmlRootNodeName);
@@ -469,6 +474,10 @@ private function selectNodeType(\DOMNode $node, $val): bool
         } elseif ($val instanceof \Traversable) {
             $this->buildXml($node, $val);
         } elseif (\is_object($val)) {
+            if (null === $this->serializer) {
+                throw new BadMethodCallException(sprintf('The serializer needs to be set to allow %s() to be used with object data.', __METHOD__));
+            }
+
             return $this->selectNodeType($node, $this->serializer->normalize($val, $this->format, $this->context));
         } elseif (is_numeric($val)) {
             return $this->appendText($node, (string) $val);
diff --git a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php
index 935e7a0323cf0..ad5090faa544a 100644
--- a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php
@@ -68,6 +68,10 @@ 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__));
+        }
+
         return '[]' === substr($type, -2)
             && $this->serializer->supportsDenormalization($data, substr($type, 0, -2), $format, $context);
     }
diff --git a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php
index c63aa00eb7455..4c88f9d799321 100644
--- a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php
@@ -93,6 +93,6 @@ public function supportsNormalization($data, string $format = null)
      */
     public function hasCacheableSupportsMethod(): bool
     {
-        return __CLASS__ === \get_class($this);
+        return __CLASS__ === static::class;
     }
 }
diff --git a/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php
index 9f72e896251c3..6bf6339372d5f 100644
--- a/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php
@@ -73,6 +73,6 @@ public function supportsDenormalization($data, string $type, string $format = nu
      */
     public function hasCacheableSupportsMethod(): bool
     {
-        return __CLASS__ === \get_class($this);
+        return __CLASS__ === static::class;
     }
 }
diff --git a/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php
index 657b5b62e89e9..979288cf6ace3 100644
--- a/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php
@@ -128,7 +128,7 @@ public function supportsDenormalization($data, string $type, string $format = nu
      */
     public function hasCacheableSupportsMethod(): bool
     {
-        return __CLASS__ === \get_class($this);
+        return __CLASS__ === static::class;
     }
 
     /**
diff --git a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php
index 37e87e3172be9..b166c84a4cc1c 100644
--- a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php
@@ -60,7 +60,7 @@ public function supportsNormalization($data, string $format = null)
      */
     public function hasCacheableSupportsMethod(): bool
     {
-        return __CLASS__ === \get_class($this);
+        return __CLASS__ === static::class;
     }
 
     /**
diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
index e33cf4cc8a356..d476040c9dd16 100644
--- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
@@ -117,7 +117,7 @@ public function supportsDenormalization($data, string $type, string $format = nu
      */
     public function hasCacheableSupportsMethod(): bool
     {
-        return __CLASS__ === \get_class($this);
+        return __CLASS__ === static::class;
     }
 
     /**
diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php
index 993f22fdffda9..2ff3f18200fb5 100644
--- a/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php
@@ -74,6 +74,6 @@ public function supportsDenormalization($data, string $type, string $format = nu
      */
     public function hasCacheableSupportsMethod(): bool
     {
-        return __CLASS__ === \get_class($this);
+        return __CLASS__ === static::class;
     }
 }
diff --git a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php
index 676034278cf6e..02cb11f44b3cb 100644
--- a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php
@@ -57,7 +57,7 @@ public function supportsDenormalization($data, string $type, string $format = nu
      */
     public function hasCacheableSupportsMethod(): bool
     {
-        return __CLASS__ === \get_class($this);
+        return __CLASS__ === static::class;
     }
 
     /**
diff --git a/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php
index 0b3167f9d224b..6d57773befe9f 100644
--- a/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php
@@ -70,6 +70,6 @@ public function denormalize($data, string $type, string $format = null, array $c
      */
     public function hasCacheableSupportsMethod(): bool
     {
-        return __CLASS__ === \get_class($this);
+        return __CLASS__ === static::class;
     }
 }
diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php
index 81fe9c1166797..e881a49d8b8a8 100644
--- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php
@@ -54,7 +54,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory
      */
     public function hasCacheableSupportsMethod(): bool
     {
-        return __CLASS__ === \get_class($this);
+        return __CLASS__ === static::class;
     }
 
     /**
diff --git a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php
index 2d12b5ec5125b..39e7502754393 100644
--- a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php
@@ -51,7 +51,7 @@ public function supportsDenormalization($data, string $type, string $format = nu
      */
     public function hasCacheableSupportsMethod(): bool
     {
-        return __CLASS__ === \get_class($this);
+        return __CLASS__ === static::class;
     }
 
     /**
diff --git a/src/Symfony/Component/String/AbstractString.php b/src/Symfony/Component/String/AbstractString.php
index e56697e391549..73daa1d495c06 100644
--- a/src/Symfony/Component/String/AbstractString.php
+++ b/src/Symfony/Component/String/AbstractString.php
@@ -31,15 +31,15 @@
  */
 abstract class AbstractString implements \JsonSerializable
 {
-    public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER;
-    public const PREG_SET_ORDER = \PREG_SET_ORDER;
-    public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE;
-    public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL;
+    public const PREG_PATTERN_ORDER = PREG_PATTERN_ORDER;
+    public const PREG_SET_ORDER = PREG_SET_ORDER;
+    public const PREG_OFFSET_CAPTURE = PREG_OFFSET_CAPTURE;
+    public const PREG_UNMATCHED_AS_NULL = PREG_UNMATCHED_AS_NULL;
 
     public const PREG_SPLIT = 0;
-    public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY;
-    public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE;
-    public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE;
+    public const PREG_SPLIT_NO_EMPTY = PREG_SPLIT_NO_EMPTY;
+    public const PREG_SPLIT_DELIM_CAPTURE = PREG_SPLIT_DELIM_CAPTURE;
+    public const PREG_SPLIT_OFFSET_CAPTURE = PREG_SPLIT_OFFSET_CAPTURE;
 
     protected $string = '';
     protected $ignoreCase = false;
@@ -262,7 +262,7 @@ public function collapseWhitespace(): self
     public function endsWith($suffix): bool
     {
         if (!\is_array($suffix) && !$suffix instanceof \Traversable) {
-            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, \get_class($this)));
+            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
         }
 
         foreach ($suffix as $s) {
@@ -317,7 +317,7 @@ public function ensureStart(string $prefix): self
     public function equalsTo($string): bool
     {
         if (!\is_array($string) && !$string instanceof \Traversable) {
-            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, \get_class($this)));
+            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
         }
 
         foreach ($string as $s) {
@@ -351,7 +351,7 @@ public function ignoreCase(): self
     public function indexOf($needle, int $offset = 0): ?int
     {
         if (!\is_array($needle) && !$needle instanceof \Traversable) {
-            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, \get_class($this)));
+            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
         }
 
         $i = \PHP_INT_MAX;
@@ -373,7 +373,7 @@ public function indexOf($needle, int $offset = 0): ?int
     public function indexOfLast($needle, int $offset = 0): ?int
     {
         if (!\is_array($needle) && !$needle instanceof \Traversable) {
-            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, \get_class($this)));
+            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
         }
 
         $i = null;
@@ -536,7 +536,7 @@ public function split(string $delimiter, int $limit = null, int $flags = null):
     public function startsWith($prefix): bool
     {
         if (!\is_array($prefix) && !$prefix instanceof \Traversable) {
-            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, \get_class($this)));
+            throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
         }
 
         foreach ($prefix as $prefix) {
diff --git a/src/Symfony/Component/String/AbstractUnicodeString.php b/src/Symfony/Component/String/AbstractUnicodeString.php
index bc3e05cd6f10c..1e1b9d7de6e95 100644
--- a/src/Symfony/Component/String/AbstractUnicodeString.php
+++ b/src/Symfony/Component/String/AbstractUnicodeString.php
@@ -139,7 +139,7 @@ public function ascii(array $rules = []): self
                 }
             } elseif (!\function_exists('iconv')) {
                 $s = preg_replace('/[^\x00-\x7F]/u', '?', $s);
-            } elseif (\ICONV_IMPL === 'glibc') {
+            } elseif (ICONV_IMPL === 'glibc') {
                 $s = iconv('UTF-8', 'ASCII//TRANSLIT', $s);
             } else {
                 $s = preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) {
@@ -223,7 +223,7 @@ public function lower(): parent
 
     public function match(string $regexp, int $flags = 0, int $offset = 0): array
     {
-        $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
+        $match = ((PREG_PATTERN_ORDER | PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
 
         if ($this->ignoreCase) {
             $regexp .= 'i';
@@ -309,7 +309,7 @@ public function replaceMatches(string $fromRegexp, $to): parent
 
         if (\is_array($to) || $to instanceof \Closure) {
             if (!\is_callable($to)) {
-                throw new \TypeError(sprintf('Argument 2 passed to %s::replaceMatches() must be callable, array given.', \get_class($this)));
+                throw new \TypeError(sprintf('Argument 2 passed to %s::replaceMatches() must be callable, array given.', static::class));
             }
 
             $replace = 'preg_replace_callback';
diff --git a/src/Symfony/Component/String/ByteString.php b/src/Symfony/Component/String/ByteString.php
index d831940e16b07..b1bb82ec222df 100644
--- a/src/Symfony/Component/String/ByteString.php
+++ b/src/Symfony/Component/String/ByteString.php
@@ -193,7 +193,7 @@ public function lower(): parent
 
     public function match(string $regexp, int $flags = 0, int $offset = 0): array
     {
-        $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
+        $match = ((PREG_PATTERN_ORDER | PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
 
         if ($this->ignoreCase) {
             $regexp .= 'i';
@@ -271,7 +271,7 @@ public function replaceMatches(string $fromRegexp, $to): parent
 
         if (\is_array($to)) {
             if (!\is_callable($to)) {
-                throw new \TypeError(sprintf('Argument 2 passed to %s::replaceMatches() must be callable, array given.', \get_class($this)));
+                throw new \TypeError(sprintf('Argument 2 passed to %s::replaceMatches() must be callable, array given.', static::class));
             }
 
             $replace = 'preg_replace_callback';
diff --git a/src/Symfony/Component/String/UnicodeString.php b/src/Symfony/Component/String/UnicodeString.php
index ada77caaae4c0..0d057a445b0f2 100644
--- a/src/Symfony/Component/String/UnicodeString.php
+++ b/src/Symfony/Component/String/UnicodeString.php
@@ -243,7 +243,7 @@ public function replace(string $from, string $to): AbstractString
                 $tail = substr($tail, \strlen($slice) + \strlen($from));
             }
 
-            $str->string = $result .= $tail;
+            $str->string = $result.$tail;
             normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
 
             if (false === $str->string) {
diff --git a/src/Symfony/Component/Translation/Command/XliffLintCommand.php b/src/Symfony/Component/Translation/Command/XliffLintCommand.php
index 1b0a77b985fa9..4376804296c76 100644
--- a/src/Symfony/Component/Translation/Command/XliffLintCommand.php
+++ b/src/Symfony/Component/Translation/Command/XliffLintCommand.php
@@ -18,6 +18,7 @@
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
 use Symfony\Component\Translation\Util\XliffUtils;
 
 /**
diff --git a/src/Symfony/Component/Translation/MessageCatalogue.php b/src/Symfony/Component/Translation/MessageCatalogue.php
index 429a3c687747b..6046eb7244ac0 100644
--- a/src/Symfony/Component/Translation/MessageCatalogue.php
+++ b/src/Symfony/Component/Translation/MessageCatalogue.php
@@ -154,9 +154,17 @@ public function replace(array $messages, string $domain = 'messages')
     public function add(array $messages, string $domain = 'messages')
     {
         if (!isset($this->messages[$domain])) {
-            $this->messages[$domain] = $messages;
-        } else {
-            foreach ($messages as $id => $message) {
+            $this->messages[$domain] = [];
+        }
+        $intlDomain = $domain;
+        $suffixLength = \strlen(self::INTL_DOMAIN_SUFFIX);
+        if (\strlen($domain) > $suffixLength && false !== strpos($domain, self::INTL_DOMAIN_SUFFIX, -$suffixLength)) {
+            $intlDomain .= self::INTL_DOMAIN_SUFFIX;
+        }
+        foreach ($messages as $id => $message) {
+            if (isset($this->messages[$intlDomain]) && \array_key_exists($id, $this->messages[$intlDomain])) {
+                $this->messages[$intlDomain][$id] = $message;
+            } else {
                 $this->messages[$domain][$id] = $message;
             }
         }
diff --git a/src/Symfony/Component/Translation/README.md b/src/Symfony/Component/Translation/README.md
index e80a70cad033e..f4f1706675d5a 100644
--- a/src/Symfony/Component/Translation/README.md
+++ b/src/Symfony/Component/Translation/README.md
@@ -3,10 +3,28 @@ Translation Component
 
 The Translation component provides tools to internationalize your application.
 
+Getting Started
+---------------
+
+```
+$ composer require symfony/translation
+```
+
+```php
+use Symfony\Component\Translation\Translator;
+
+$translator = new Translator('fr_FR');
+$translator->addResource('array', [
+    'Hello World!' => 'Bonjour !',
+], 'fr_FR');
+
+echo $translator->trans('Hello World!'); // outputs « Bonjour ! »
+```
+
 Resources
 ---------
 
-  * [Documentation](https://symfony.com/doc/current/components/translation.html)
+  * [Documentation](https://symfony.com/doc/current/translation.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/Translation/Tests/Catalogue/TargetOperationTest.php b/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php
index 570b503aeabaa..d4608f6ef6311 100644
--- a/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php
+++ b/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php
@@ -67,6 +67,19 @@ public function testGetResultFromIntlDomain()
         );
     }
 
+    public function testGetResultWithMixedDomains()
+    {
+        $this->assertEquals(
+            new MessageCatalogue('en', [
+                'messages+intl-icu' => ['a' => 'old_a'],
+            ]),
+            $this->createOperation(
+                new MessageCatalogue('en', ['messages' => ['a' => 'old_a']]),
+                new MessageCatalogue('en', ['messages+intl-icu' => ['a' => 'new_a']])
+            )->getResult()
+        );
+    }
+
     public function testGetResultWithMetadata()
     {
         $leftCatalogue = new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b']]);
diff --git a/src/Symfony/Component/Validator/Constraint.php b/src/Symfony/Component/Validator/Constraint.php
index b349d8675d847..b81fb9f5b1201 100644
--- a/src/Symfony/Component/Validator/Constraint.php
+++ b/src/Symfony/Component/Validator/Constraint.php
@@ -70,7 +70,7 @@ abstract class Constraint
     public static function getErrorName($errorCode)
     {
         if (!isset(static::$errorNames[$errorCode])) {
-            throw new InvalidArgumentException(sprintf('The error code "%s" does not exist for constraint of type "%s".', $errorCode, \get_called_class()));
+            throw new InvalidArgumentException(sprintf('The error code "%s" does not exist for constraint of type "%s".', $errorCode, static::class));
         }
 
         return static::$errorNames[$errorCode];
@@ -115,7 +115,7 @@ public function __construct($options = null)
 
         if (\is_array($options) && isset($options['value']) && !property_exists($this, 'value')) {
             if (null === $defaultOption) {
-                throw new ConstraintDefinitionException(sprintf('No default option is configured for constraint "%s".', \get_class($this)));
+                throw new ConstraintDefinitionException(sprintf('No default option is configured for constraint "%s".', static::class));
             }
 
             $options[$defaultOption] = $options['value'];
@@ -136,7 +136,7 @@ public function __construct($options = null)
             }
         } elseif (null !== $options && !(\is_array($options) && 0 === \count($options))) {
             if (null === $defaultOption) {
-                throw new ConstraintDefinitionException(sprintf('No default option is configured for constraint "%s".', \get_class($this)));
+                throw new ConstraintDefinitionException(sprintf('No default option is configured for constraint "%s".', static::class));
             }
 
             if (\array_key_exists($defaultOption, $knownOptions)) {
@@ -148,11 +148,11 @@ public function __construct($options = null)
         }
 
         if (\count($invalidOptions) > 0) {
-            throw new InvalidOptionsException(sprintf('The options "%s" do not exist in constraint "%s".', implode('", "', $invalidOptions), \get_class($this)), $invalidOptions);
+            throw new InvalidOptionsException(sprintf('The options "%s" do not exist in constraint "%s".', implode('", "', $invalidOptions), static::class), $invalidOptions);
         }
 
         if (\count($missingOptions) > 0) {
-            throw new MissingOptionsException(sprintf('The options "%s" must be set for constraint "%s".', implode('", "', array_keys($missingOptions)), \get_class($this)), array_keys($missingOptions));
+            throw new MissingOptionsException(sprintf('The options "%s" must be set for constraint "%s".', implode('", "', array_keys($missingOptions)), static::class), array_keys($missingOptions));
         }
     }
 
@@ -175,7 +175,7 @@ public function __set(string $option, $value)
             return;
         }
 
-        throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint "%s".', $option, \get_class($this)), [$option]);
+        throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint "%s".', $option, static::class), [$option]);
     }
 
     /**
@@ -201,7 +201,7 @@ public function __get(string $option)
             return $this->groups;
         }
 
-        throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint "%s".', $option, \get_class($this)), [$option]);
+        throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint "%s".', $option, static::class), [$option]);
     }
 
     /**
@@ -265,7 +265,7 @@ public function getRequiredOptions()
      */
     public function validatedBy()
     {
-        return \get_class($this).'Validator';
+        return static::class.'Validator';
     }
 
     /**
diff --git a/src/Symfony/Component/Validator/ConstraintValidator.php b/src/Symfony/Component/Validator/ConstraintValidator.php
index 4d66776839bed..02a8261eff54e 100644
--- a/src/Symfony/Component/Validator/ConstraintValidator.php
+++ b/src/Symfony/Component/Validator/ConstraintValidator.php
@@ -21,8 +21,8 @@
 abstract class ConstraintValidator implements ConstraintValidatorInterface
 {
     /**
-     * Whether to format {@link \DateTime} objects as RFC-3339 dates
-     * ("Y-m-d H:i:s").
+     * Whether to format {@link \DateTime} objects, either with the {@link \IntlDateFormatter}
+     * (if it is available) or as RFC-3339 dates ("Y-m-d H:i:s").
      */
     const PRETTY_DATE = 1;
 
@@ -69,7 +69,8 @@ protected function formatTypeOf($value)
      * in double quotes ("). Objects, arrays and resources are formatted as
      * "object", "array" and "resource". If the $format bitmask contains
      * the PRETTY_DATE bit, then {@link \DateTime} objects will be formatted
-     * as RFC-3339 dates ("Y-m-d H:i:s").
+     * with the {@link \IntlDateFormatter}. If it is not available, they will be
+     * formatted as RFC-3339 dates ("Y-m-d H:i:s").
      *
      * Be careful when passing message parameters to a constraint violation
      * that (may) contain objects, arrays or resources. These parameters
diff --git a/src/Symfony/Component/Validator/Constraints/AbstractComparison.php b/src/Symfony/Component/Validator/Constraints/AbstractComparison.php
index 435ced6eb8486..9b5281296a17e 100644
--- a/src/Symfony/Component/Validator/Constraints/AbstractComparison.php
+++ b/src/Symfony/Component/Validator/Constraints/AbstractComparison.php
@@ -39,15 +39,15 @@ public function __construct($options = null)
 
         if (\is_array($options)) {
             if (!isset($options['value']) && !isset($options['propertyPath'])) {
-                throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires either the "value" or "propertyPath" option to be set.', \get_class($this)));
+                throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires either the "value" or "propertyPath" option to be set.', static::class));
             }
 
             if (isset($options['value']) && isset($options['propertyPath'])) {
-                throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "value" or "propertyPath" options to be set, not both.', \get_class($this)));
+                throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "value" or "propertyPath" options to be set, not both.', static::class));
             }
 
             if (isset($options['propertyPath']) && !class_exists(PropertyAccess::class)) {
-                throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', \get_class($this)));
+                throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', static::class));
             }
         }
 
diff --git a/src/Symfony/Component/Validator/Constraints/Composite.php b/src/Symfony/Component/Validator/Constraints/Composite.php
index 18ea5e319f7f1..9c423b0dc376a 100644
--- a/src/Symfony/Component/Validator/Constraints/Composite.php
+++ b/src/Symfony/Component/Validator/Constraints/Composite.php
@@ -71,11 +71,11 @@ public function __construct($options = null)
                     $constraint = \get_class($constraint);
                 }
 
-                throw new ConstraintDefinitionException(sprintf('The value %s is not an instance of Constraint in constraint %s', $constraint, \get_class($this)));
+                throw new ConstraintDefinitionException(sprintf('The value %s is not an instance of Constraint in constraint %s', $constraint, static::class));
             }
 
             if ($constraint instanceof Valid) {
-                throw new ConstraintDefinitionException(sprintf('The constraint Valid cannot be nested inside constraint %s. You can only declare the Valid constraint directly on a field or method.', \get_class($this)));
+                throw new ConstraintDefinitionException(sprintf('The constraint Valid cannot be nested inside constraint %s. You can only declare the Valid constraint directly on a field or method.', static::class));
             }
         }
 
@@ -99,7 +99,7 @@ public function __construct($options = null)
                 $excessGroups = array_diff($constraint->groups, $this->groups);
 
                 if (\count($excessGroups) > 0) {
-                    throw new ConstraintDefinitionException(sprintf('The group(s) "%s" passed to the constraint %s should also be passed to its containing constraint %s', implode('", "', $excessGroups), \get_class($constraint), \get_class($this)));
+                    throw new ConstraintDefinitionException(sprintf('The group(s) "%s" passed to the constraint %s should also be passed to its containing constraint %s', implode('", "', $excessGroups), \get_class($constraint), static::class));
                 }
             } else {
                 $constraint->groups = $this->groups;
diff --git a/src/Symfony/Component/Validator/Constraints/NumberConstraintTrait.php b/src/Symfony/Component/Validator/Constraints/NumberConstraintTrait.php
index ff189cf3e8acb..2913f7e882d72 100644
--- a/src/Symfony/Component/Validator/Constraints/NumberConstraintTrait.php
+++ b/src/Symfony/Component/Validator/Constraints/NumberConstraintTrait.php
@@ -27,11 +27,11 @@ private function configureNumberConstraintOptions($options): array
         }
 
         if (isset($options['propertyPath'])) {
-            throw new ConstraintDefinitionException(sprintf('The "propertyPath" option of the "%s" constraint cannot be set.', \get_class($this)));
+            throw new ConstraintDefinitionException(sprintf('The "propertyPath" option of the "%s" constraint cannot be set.', static::class));
         }
 
         if (isset($options['value'])) {
-            throw new ConstraintDefinitionException(sprintf('The "value" option of the "%s" constraint cannot be set.', \get_class($this)));
+            throw new ConstraintDefinitionException(sprintf('The "value" option of the "%s" constraint cannot be set.', static::class));
         }
 
         $options['value'] = 0;
diff --git a/src/Symfony/Component/Validator/Constraints/Range.php b/src/Symfony/Component/Validator/Constraints/Range.php
index 9f05edf677ffb..a916a9625b9e0 100644
--- a/src/Symfony/Component/Validator/Constraints/Range.php
+++ b/src/Symfony/Component/Validator/Constraints/Range.php
@@ -50,15 +50,15 @@ public function __construct($options = null)
     {
         if (\is_array($options)) {
             if (isset($options['min']) && isset($options['minPropertyPath'])) {
-                throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "min" or "minPropertyPath" options to be set, not both.', \get_class($this)));
+                throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "min" or "minPropertyPath" options to be set, not both.', static::class));
             }
 
             if (isset($options['max']) && isset($options['maxPropertyPath'])) {
-                throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "max" or "maxPropertyPath" options to be set, not both.', \get_class($this)));
+                throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "max" or "maxPropertyPath" options to be set, not both.', static::class));
             }
 
             if ((isset($options['minPropertyPath']) || isset($options['maxPropertyPath'])) && !class_exists(PropertyAccess::class)) {
-                throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "minPropertyPath" or "maxPropertyPath" option.', \get_class($this)));
+                throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "minPropertyPath" or "maxPropertyPath" option.', static::class));
             }
         }
 
diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php
index a07a608c3af4d..afcbec490b0c4 100644
--- a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php
+++ b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php
@@ -14,7 +14,6 @@
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\Constraints\GroupSequence;
 use Symfony\Component\Validator\Constraints\Traverse;
-use Symfony\Component\Validator\Constraints\Valid;
 use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
 use Symfony\Component\Validator\Exception\GroupDefinitionException;
 
@@ -178,10 +177,6 @@ public function addConstraint(Constraint $constraint)
             throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
         }
 
-        if ($constraint instanceof Valid) {
-            throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
-        }
-
         if ($constraint instanceof Traverse) {
             if ($constraint->traverse) {
                 // If traverse is true, traversal should be explicitly enabled
diff --git a/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php b/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php
index 2a8d3f4f56eeb..23c0f73562f70 100644
--- a/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php
+++ b/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php
@@ -50,8 +50,21 @@ public function getPropertyValue($object)
     {
         $reflProperty = $this->getReflectionMember($object);
 
-        if (\PHP_VERSION_ID >= 70400 && !$reflProperty->isInitialized($object)) {
-            return null;
+        if (\PHP_VERSION_ID >= 70400 && $reflProperty->hasType() && !$reflProperty->isInitialized($object)) {
+            // There is no way to check if a property has been unset or if it is uninitialized.
+            // When trying to access an uninitialized property, __get method is triggered.
+
+            // If __get method is not present, no fallback is possible
+            // Otherwise we need to catch an Error in case we are trying to access an uninitialized but set property.
+            if (!method_exists($object, '__get')) {
+                return null;
+            }
+
+            try {
+                return $reflProperty->getValue($object);
+            } catch (\Error $e) {
+                return null;
+            }
         }
 
         return $reflProperty->getValue($object);
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf
index 8ee3120482267..a546b86c78a9b 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf
@@ -366,6 +366,14 @@
                 This value should be between {{ min }} and {{ max }}.
                 Dieser Wert sollte zwischen {{ min }} und {{ max }} sein.
             
+            
+                This value is not a valid hostname.
+                Dieser Wert ist kein gültiger Hostname.
+            
+            
+                The number of elements in this collection should be a multiple of {{ compared_value }}.
+                Die Anzahl an Elementen in dieser Sammlung sollte ein Vielfaches von {{ compared_value }} sein.
+            
         
     
 
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf
index 100d552076f2c..8f8d2d0a0fe98 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf
@@ -366,6 +366,14 @@
                 This value should be between {{ min }} and {{ max }}.
                 This value should be between {{ min }} and {{ max }}.
             
+            
+                This value is not a valid hostname.
+                This value is not a valid hostname.
+            
+            
+                The number of elements in this collection should be a multiple of {{ compared_value }}.
+                The number of elements in this collection should be a multiple of {{ compared_value }}.
+            
         
     
 
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf
index dc7e73e3c7581..e54be35c15cca 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf
@@ -366,6 +366,14 @@
                 This value should be between {{ min }} and {{ max }}.
                 Cette valeur doit être comprise entre {{ min }} et {{ max }}.
             
+            
+                This value is not a valid hostname.
+                Cette valeur n'est pas un nom d'hôte valide.
+            
+            
+                The number of elements in this collection should be a multiple of {{ compared_value }}.
+                Le nombre d'éléments de cette collection doit être un multiple de {{ compared_value }}.
+            
         
     
 
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.mn.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.mn.xlf
index 4be2198aa3b27..b1458eee1ac0b 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.mn.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.mn.xlf
@@ -146,6 +146,226 @@
                 This value is not a valid language.
                 Энэ утга үнэн зөв хэл биш байна .
             
+            
+                This value is not a valid country.
+                Энэ утга үнэн бодит улс биш байна.
+            
+            
+                This value is already used.
+                Энэ утга аль хэдийнээ хэрэглэгдсэн байна.
+            
+            
+                The size of the image could not be detected.
+                Зургийн хэмжээ тогтоогдож чадсангүй.
+            
+            
+                The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.
+                Зургийн өргөн хэтэрхий том байна ({{ width }}px). Өргөн нь хамгийн ихдээ {{ max_width }}px байх боломжтой.
+            
+            
+                The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.
+                Зургийн өргөн хэтэрхий жижиг байна ({{ width }}px). Өргөн нь хамгийн багадаа {{ min_width }}px байх боломжтой.
+            
+            
+                The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.
+                Зургийн өндөр хэтэрхий том байна ({{ height }}px). Өндөр нь хамгийн ихдээ {{ max_height }}px байх боломжтой.
+            
+            
+                The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.
+                Зургийн өндөр хэтэрхий жижиг байна ({{ height }}px). Өндөр нь хамгийн багадаа {{ min_height }}px байх боломжтой.
+            
+            
+                This value should be the user's current password.
+                Энэ утга хэрэглэгчийн одоогийн нууц үг байх ёстой.
+            
+            
+                This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.
+                Энэ утга яг {{ limit }} тэмдэгт байх ёстой.|Энэ утга яг {{ limit }} тэмдэгт байх ёстой.
+            
+            
+                The file was only partially uploaded.
+                Файлын зөвхөн хагас нь upload хийгдсэн.
+            
+            
+                No file was uploaded.
+                Ямар ч файл upload хийгдсэнгүй.
+            
+            
+                No temporary folder was configured in php.ini.
+                php.ini дээр түр зуурын хавтсыг тохируулаагүй байна, эсвэл тохируулсан хавтас байхгүй байна.
+            
+            
+                Cannot write temporary file to disk.
+                Түр зуурын файлыг диск руу бичиж болохгүй байна.
+            
+            
+                A PHP extension caused the upload to fail.
+                PHP extension нь upload -г амжилтгүй болгоод байна.
+            
+            
+                This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.
+                Энэ коллекц {{ limit }} ба түүнээс дээш тооны элемент агуулах ёстой.|Энэ коллекц {{ limit }} ба түүнээс дээш тооны элемент агуулах ёстой.
+            
+            
+                This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.
+                Энэ коллекц {{ limit }} ба түүнээс доош тооны элемент агуулах ёстой.|Энэ коллекц {{ limit }} ба түүнээс доош тооны элемент агуулах ёстой.
+            
+            
+                This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.
+                Энэ коллекц яг {{ limit }} элемент агуулах ёстой.|Энэ коллекц яг {{ limit }} элемент агуулах ёстой.
+            
+            
+                Invalid card number.
+                Картын дугаар буруу байна.
+            
+            
+                Unsupported card type or invalid card number.
+                Дэмжигдээгүй картын төрөл эсвэл картын дугаар буруу байна.
+            
+            
+                This is not a valid International Bank Account Number (IBAN).
+                Энэ утга үнэн зөв Олон Улсын Банкны Дансны Дугаар (IBAN) биш байна.
+            
+            
+                This value is not a valid ISBN-10.
+                Энэ утга үнэн зөв ISBN-10 биш байна.
+            
+            
+                This value is not a valid ISBN-13.
+                Энэ утга үнэн зөв ISBN-13 биш байна.
+            
+            
+                This value is neither a valid ISBN-10 nor a valid ISBN-13.
+                Энэ утга үнэн зөв ISBN-10 юмуу ISBN-13 биш байна.
+            
+            
+                This value is not a valid ISSN.
+                Энэ утга үнэн зөв ISSN биш байна.
+            
+            
+                This value is not a valid currency.
+                Энэ утга үнэн бодит валют биш байна.
+            
+            
+                This value should be equal to {{ compared_value }}.
+                Энэ утга {{ compared_value }} -тaй тэнцүү байх ёстой.
+            
+            
+                This value should be greater than {{ compared_value }}.
+                Энэ утга {{ compared_value }} -с их байх ёстой.
+            
+            
+                This value should be greater than or equal to {{ compared_value }}.
+                Энэ утга {{ compared_value }} -тай тэнцүү юмуу эсвэл их байх ёстой.
+            
+            
+                This value should be identical to {{ compared_value_type }} {{ compared_value }}.
+                Энэ утга {{ compared_value_type }} {{ compared_value }} -тай яг ижил байх ёстой.
+            
+            
+                This value should be less than {{ compared_value }}.
+                Энэ утга {{ compared_value }} -с бага байх ёстой.
+            
+            
+                This value should be less than or equal to {{ compared_value }}.
+                Энэ утга {{ compared_value }} -тай ижил юмуу эсвэл бага байх ёстой.
+            
+            
+                This value should not be equal to {{ compared_value }}.
+                Энэ утга {{ compared_value }} -тай тэнцүү байх ёсгүй.
+            
+            
+                This value should not be identical to {{ compared_value_type }} {{ compared_value }}.
+                Энэ утга {{ compared_value_type }} {{ compared_value }} -тай яг ижил байх ёсгүй.
+            
+            
+                The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.
+                Зургийн харьцаа хэтэрхий том байна ({{ ratio }}). Харьцаа нь хамгийн ихдээ {{ max_ratio }} байна.
+            
+            
+                The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.
+                Зургийн харьцаа хэтэрхий жижиг байна ({{ ratio }}). Харьцаа нь хамгийн багадаа {{ min_ratio }} байна.
+            
+            
+                The image is square ({{ width }}x{{ height }}px). Square images are not allowed.
+                Зураг дөрвөлжин хэлбэртэй байна ({{ width }}x{{ height }}px). Дөрвөлжин зургууд оруулах боломжгүй.
+            
+            
+                The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed.
+                Зураг хэвтээ байрлалтай байна ({{ width }}x{{ height }}px). Хэвтээ байрлалтай зургууд оруулах боломжгүй.
+            
+            
+                The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.
+                Зургууд босоо байрлалтай байна ({{ width }}x{{ height }}px). Босоо байрлалтай зургууд оруулах боломжгүй.
+            
+            
+                An empty file is not allowed.
+                Хоосон файл оруулах боломжгүй.
+            
+            
+                The host could not be resolved.
+                Хост зөв тохирогдоогүй байна.
+            
+            
+                This value does not match the expected {{ charset }} charset.
+                Энэ утга тооцоолсон {{ charset }} тэмдэгттэй таарахгүй байна.
+            
+            
+                This is not a valid Business Identifier Code (BIC).
+                Энэ утга үнэн зөв Business Identifier Code (BIC) биш байна.
+            
+            
+                Error
+                Алдаа
+            
+            
+                This is not a valid UUID.
+                Энэ утга үнэн зөв UUID биш байна.
+            
+            
+                This value should be a multiple of {{ compared_value }}.
+                Энэ утга {{ compared_value }} -н үржвэр байх ёстой.
+            
+            
+                This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.
+                Энэ Business Identifier Code (BIC) код нь IBAN {{ iban }} -тай холбоогүй байна.
+            
+            
+                This value should be valid JSON.
+                Энэ утга JSON байх ёстой.
+            
+            
+                This collection should contain only unique elements.
+                Энэ коллекц зөвхөн давтагдахгүй элементүүд агуулах ёстой.
+            
+            
+                This value should be positive.
+                Энэ утга эерэг байх ёстой.
+            
+            
+                This value should be either positive or zero.
+                Энэ утга тэг эсвэл эерэг байх ёстой.
+            
+            
+                This value should be negative.
+                Энэ утга сөрөг байх ёстой.
+            
+            
+                This value should be either negative or zero.
+                Энэ утга сөрөг эсвэл тэг байх ёстой.
+            
+            
+                This value is not a valid timezone.
+                Энэ утга үнэн зөв цагийн бүс биш байна.
+            
+            
+                This password has been leaked in a data breach, it must not be used. Please use another password.
+                Энэ нууц үгийн мэдээлэл алдагдсан байх магадлалтай учраас дахин ашиглагдах ёсгүй. Өөр нууц үг ашиглана уу.
+            
+            
+                This value should be between {{ min }} and {{ max }}.
+                Энэ утга {{ min }} -с {{ max }} хооронд байх ёстой.
+            
         
     
 
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf
index f1910c99d5751..d1dd1471a77ca 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf
@@ -330,10 +330,46 @@
                 This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.
                 Ten kod BIC (Business Identifier Code) nie jest powiązany z międzynarodowym numerem rachunku bankowego (IBAN) {{ iban }}.
             
+            
+                This value should be valid JSON.
+                Ta wartość powinna być prawidłowym formatem JSON.
+            
+            
+                This collection should contain only unique elements.
+                Ten zbiór powinien zawierać tylko unikalne elementy.
+            
+            
+                This value should be positive.
+                Ta wartość powinna być dodatnia.
+            
+            
+                This value should be either positive or zero.
+                Ta wartość powinna być dodatnia lub równa zero.
+            
+            
+                This value should be negative.
+                Ta wartość powinna być ujemna.
+            
+            
+                This value should be either negative or zero.
+                Ta wartość powinna być ujemna lub równa zero.
+            
+            
+                This value is not a valid timezone.
+                Ta wartość nie jest prawidłową strefą czasową.
+            
+            
+                This password has been leaked in a data breach, it must not be used. Please use another password.
+                To hasło wyciekło w wyniku naruszenia danych i nie może być użyte. Proszę użyć innego hasła.
+            
             
                 This value should be between {{ min }} and {{ max }}.
                 Ta wartość powinna być pomiędzy {{ min }} a {{ max }}.
             
+            
+                This value is not a valid hostname.
+                Ta wartość nie jest prawidłową nazwą hosta.
+            
         
     
 
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf
index 361be20f796f8..80911a9902910 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf
@@ -366,6 +366,10 @@
                 This value should be between {{ min }} and {{ max }}.
                 Значение должно быть между {{ min }} и {{ max }}.
             
+            
+                This value is not a valid hostname.
+                Значение не является корректным именем хоста.
+            
         
     
 
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf
index 95dd7d6665997..ead79d2cd7e5b 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf
@@ -362,6 +362,18 @@
                 This password has been leaked in a data breach, it must not be used. Please use another password.
                 Mật khẩu này đã bị rò rỉ dữ liệu, không được sử dụng nữa. Xin vui lòng sử dụng mật khẩu khác.
            
+           
+               This value should be between {{ min }} and {{ max }}.
+               Giá trị này nên thuộc giữa {{ min }} và {{ max }}.
+           
+           
+               This value is not a valid hostname.
+               Giá trị này không phải là tên máy chủ hợp lệ.
+           
+           
+               The number of elements in this collection should be a multiple of {{ compared_value }}.
+               Số lượng các phần tử trong bộ sưu tập này nên là bội số của {{compared_value}}.
+           
         
     
 
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php
index 34d280b5da55c..280757b8e935f 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php
@@ -12,7 +12,6 @@
 namespace Symfony\Component\Validator\Tests\Constraints;
 
 use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
-use Symfony\Component\PropertyAccess\PropertyAccess;
 use Symfony\Component\Validator\Constraints\Expression;
 use Symfony\Component\Validator\Constraints\ExpressionValidator;
 use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Entity_74_Proxy.php b/src/Symfony/Component/Validator/Tests/Fixtures/Entity_74_Proxy.php
new file mode 100644
index 0000000000000..d74badc7d7f96
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Fixtures/Entity_74_Proxy.php
@@ -0,0 +1,18 @@
+uninitialized);
+    }
+
+    public function __get($name)
+    {
+        return 42;
+    }
+}
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php
index 6eb82b7585b6f..2cda5c6310b21 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php
@@ -15,7 +15,6 @@
 use Psr\Cache\CacheItemPoolInterface;
 use Symfony\Component\Cache\Adapter\ArrayAdapter;
 use Symfony\Component\Validator\Constraints\Callback;
-use Symfony\Component\Validator\Mapping\Cache\Psr6Cache;
 use Symfony\Component\Validator\Mapping\ClassMetadata;
 use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
 use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php
index 8d9e67881a9dc..8868ec64aac9f 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php
@@ -15,11 +15,13 @@
 use Symfony\Component\Validator\Mapping\PropertyMetadata;
 use Symfony\Component\Validator\Tests\Fixtures\Entity;
 use Symfony\Component\Validator\Tests\Fixtures\Entity_74;
+use Symfony\Component\Validator\Tests\Fixtures\Entity_74_Proxy;
 
 class PropertyMetadataTest extends TestCase
 {
     const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
     const CLASSNAME_74 = 'Symfony\Component\Validator\Tests\Fixtures\Entity_74';
+    const CLASSNAME_74_PROXY = 'Symfony\Component\Validator\Tests\Fixtures\Entity_74_Proxy';
     const PARENTCLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent';
 
     public function testInvalidPropertyName()
@@ -66,4 +68,17 @@ public function testGetPropertyValueFromUninitializedProperty()
 
         $this->assertNull($metadata->getPropertyValue($entity));
     }
+
+    /**
+     * @requires PHP 7.4
+     */
+    public function testGetPropertyValueFromUninitializedPropertyShouldNotReturnNullIfMagicGetIsPresent()
+    {
+        $entity = new Entity_74_Proxy();
+        $metadata = new PropertyMetadata(self::CLASSNAME_74_PROXY, 'uninitialized');
+        $notUnsetMetadata = new PropertyMetadata(self::CLASSNAME_74_PROXY, 'notUnset');
+
+        $this->assertNull($notUnsetMetadata->getPropertyValue($entity));
+        $this->assertEquals(42, $metadata->getPropertyValue($entity));
+    }
 }
diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json
index f0c3a83c447f0..3273b3745528e 100644
--- a/src/Symfony/Component/Validator/composer.json
+++ b/src/Symfony/Component/Validator/composer.json
@@ -31,6 +31,7 @@
         "symfony/dependency-injection": "^4.4|^5.0",
         "symfony/expression-language": "^4.4|^5.0",
         "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/translation": "^4.4|^5.0",
diff --git a/src/Symfony/Component/VarDumper/Caster/DateCaster.php b/src/Symfony/Component/VarDumper/Caster/DateCaster.php
index e36fbe3d28ae1..e3708b7fb3276 100644
--- a/src/Symfony/Component/VarDumper/Caster/DateCaster.php
+++ b/src/Symfony/Component/VarDumper/Caster/DateCaster.php
@@ -35,7 +35,11 @@ public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub,
             .($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '')
         ;
 
-        $a = [];
+        unset(
+            $a[Caster::PREFIX_DYNAMIC.'date'],
+            $a[Caster::PREFIX_DYNAMIC.'timezone'],
+            $a[Caster::PREFIX_DYNAMIC.'timezone_type']
+        );
         $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title);
 
         $stub->class .= $d->format(' @U');
diff --git a/src/Symfony/Component/VarDumper/Cloner/Stub.php b/src/Symfony/Component/VarDumper/Cloner/Stub.php
index 7e9eb6d59f519..7f6d05d33dbda 100644
--- a/src/Symfony/Component/VarDumper/Cloner/Stub.php
+++ b/src/Symfony/Component/VarDumper/Cloner/Stub.php
@@ -48,7 +48,7 @@ public function __sleep(): array
     {
         $properties = [];
 
-        if (!isset(self::$defaultProperties[$c = \get_class($this)])) {
+        if (!isset(self::$defaultProperties[$c = static::class])) {
             self::$defaultProperties[$c] = get_class_vars($c);
 
             foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) {
diff --git a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php
index 4c653d627df3b..f02a59d5670c0 100644
--- a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php
+++ b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php
@@ -28,7 +28,6 @@ protected function doClone($var)
         $pos = 0;                       // Number of cloned items past the minimum depth
         $refsCounter = 0;               // Hard references counter
         $queue = [[$var]];    // This breadth-first queue is the return value
-        $indexedArrays = [];       // Map of queue indexes that hold numerically indexed arrays
         $hardRefs = [];            // Map of original zval ids to stub objects
         $objRefs = [];             // Map of original object handles to their stub object counterpart
         $objects = [];             // Keep a ref to objects to ensure their handle cannot be reused while cloning
diff --git a/src/Symfony/Component/VarDumper/Resources/functions/dump.php b/src/Symfony/Component/VarDumper/Resources/functions/dump.php
index e1543a8df8fe9..a485d573a007a 100644
--- a/src/Symfony/Component/VarDumper/Resources/functions/dump.php
+++ b/src/Symfony/Component/VarDumper/Resources/functions/dump.php
@@ -38,6 +38,6 @@ function dd(...$vars)
             VarDumper::dump($v);
         }
 
-        die(1);
+        exit(1);
     }
 }
diff --git a/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php b/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php
index 258419c8f3c9a..33d60c020196b 100644
--- a/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php
+++ b/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php
@@ -52,9 +52,6 @@ public function assertDumpMatchesFormat($expected, $data, int $filter = 0, strin
         $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message);
     }
 
-    /**
-     * @return string|null
-     */
     protected function getDump($data, $key = null, int $filter = 0): ?string
     {
         if (null === $flags = $this->varDumperConfig['flags']) {
diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php
index dbfd3fd8cb1e3..796a31e4e041d 100644
--- a/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php
+++ b/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php
@@ -16,6 +16,7 @@
 use Symfony\Component\VarDumper\Caster\DateCaster;
 use Symfony\Component\VarDumper\Cloner\Stub;
 use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+use Symfony\Component\VarDumper\Tests\Fixtures\DateTimeChild;
 
 /**
  * @author Dany Maillard 
@@ -47,7 +48,7 @@ public function testCastDateTime($time, $timezone, $xDate, $xTimestamp, $xInfos)
     {
         $stub = new Stub();
         $date = new \DateTime($time, new \DateTimeZone($timezone));
-        $cast = DateCaster::castDateTime($date, ['foo' => 'bar'], $stub, false, 0);
+        $cast = DateCaster::castDateTime($date, Caster::castObject($date, \DateTime::class), $stub, false, 0);
 
         $xDump = << "foo"
+  "\\x00~\\x00date" => $xDate
+]
+EODUMP;
+
+        $this->assertDumpEquals($xDump, $dateCast);
+
+        $xDump = <<assertDumpMatchesFormat($xDump, $dateCast["\0~\0date"]);
+    }
+
     /**
      * @dataProvider provideIntervals
      */
diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/DateTimeChild.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/DateTimeChild.php
new file mode 100644
index 0000000000000..2ea2df6514ae0
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/DateTimeChild.php
@@ -0,0 +1,8 @@
+ ['ratio' => 'compress', 'rankdir' => 'LR'],
-        'node' => ['fontsize' => 9, 'fontname' => 'Arial', 'color' => '#333333', 'fillcolor' => 'lightblue', 'fixedsize' => 'false', 'width' => 1],
-        'edge' => ['fontsize' => 9, 'fontname' => 'Arial', 'color' => '#333333', 'arrowhead' => 'normal', 'arrowsize' => 0.5],
+        'node' => ['fontsize' => '9', 'fontname' => 'Arial', 'color' => '#333333', 'fillcolor' => 'lightblue', 'fixedsize' => 'false', 'width' => '1'],
+        'edge' => ['fontsize' => '9', 'fontname' => 'Arial', 'color' => '#333333', 'arrowhead' => 'normal', 'arrowsize' => '0.5'],
     ];
 
     /**
diff --git a/src/Symfony/Component/Workflow/Event/Event.php b/src/Symfony/Component/Workflow/Event/Event.php
index a0bb4c5176cfe..cda6e170becd5 100644
--- a/src/Symfony/Component/Workflow/Event/Event.php
+++ b/src/Symfony/Component/Workflow/Event/Event.php
@@ -60,7 +60,7 @@ public function getWorkflowName()
         return $this->workflow->getName();
     }
 
-    public function getMetadata(string $key, object $subject)
+    public function getMetadata(string $key, $subject)
     {
         return $this->workflow->getMetadataStore()->getMetadata($key, $subject);
     }
diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php
index e5e0f2a4bd5b1..de153c34f4bca 100644
--- a/src/Symfony/Component/Yaml/Inline.php
+++ b/src/Symfony/Component/Yaml/Inline.php
@@ -589,6 +589,10 @@ private static function evaluateScalar(string $scalar, int $flags, array $refere
                         return substr($scalar, 2);
                     case 0 === strpos($scalar, '!php/object'):
                         if (self::$objectSupport) {
+                            if (!isset($scalar[12])) {
+                                return false;
+                            }
+
                             return unserialize(self::parseScalar(substr($scalar, 12)));
                         }
 
@@ -599,6 +603,10 @@ private static function evaluateScalar(string $scalar, int $flags, array $refere
                         return null;
                     case 0 === strpos($scalar, '!php/const'):
                         if (self::$constantSupport) {
+                            if (!isset($scalar[11])) {
+                                return '';
+                            }
+
                             $i = 0;
                             if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) {
                                 return \constant($const);
diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php
index 0f46f75ee72a7..79c1df6d7614a 100644
--- a/src/Symfony/Component/Yaml/Tests/InlineTest.php
+++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php
@@ -738,6 +738,49 @@ public function getTestsForOctalNumbers()
         ];
     }
 
+    /**
+     * @dataProvider phpObjectTagWithEmptyValueProvider
+     */
+    public function testPhpObjectWithEmptyValue($expected, $value)
+    {
+        $this->assertSame($expected, Inline::parse($value, Yaml::PARSE_OBJECT));
+    }
+
+    public function phpObjectTagWithEmptyValueProvider()
+    {
+        return [
+            [false, '!php/object'],
+            [false, '!php/object '],
+            [false, '!php/object  '],
+            [[false], '[!php/object]'],
+            [[false], '[!php/object ]'],
+            [[false, 'foo'], '[!php/object  , foo]'],
+        ];
+    }
+
+    /**
+     * @dataProvider phpConstTagWithEmptyValueProvider
+     */
+    public function testPhpConstTagWithEmptyValue($expected, $value)
+    {
+        $this->assertSame($expected, Inline::parse($value, Yaml::PARSE_CONSTANT));
+    }
+
+    public function phpConstTagWithEmptyValueProvider()
+    {
+        return [
+            ['', '!php/const'],
+            ['', '!php/const '],
+            ['', '!php/const  '],
+            [[''], '[!php/const]'],
+            [[''], '[!php/const ]'],
+            [['', 'foo'], '[!php/const  , foo]'],
+            [['' => 'foo'], '{!php/const: foo}'],
+            [['' => 'foo'], '{!php/const : foo}'],
+            [['' => 'foo', 'bar' => 'ccc'], '{!php/const  : foo, bar: ccc}'],
+        ];
+    }
+
     /**
      * @dataProvider unquotedExclamationMarkThrowsProvider
      */
diff --git a/src/Symfony/Contracts/Cache/CacheTrait.php b/src/Symfony/Contracts/Cache/CacheTrait.php
index 355ea2962ebdc..eda3d4931a158 100644
--- a/src/Symfony/Contracts/Cache/CacheTrait.php
+++ b/src/Symfony/Contracts/Cache/CacheTrait.php
@@ -41,8 +41,7 @@ public function delete(string $key): bool
     private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null)
     {
         if (0 > $beta = $beta ?? 1.0) {
-            throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', \get_class($this), $beta)) extends \InvalidArgumentException implements InvalidArgumentException {
-            };
+            throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException { };
         }
 
         $item = $pool->getItem($key);
diff --git a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
index aad712e3e8dcd..de13686819fc6 100644
--- a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
+++ b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
@@ -782,6 +782,18 @@ public function testDestruct()
         $this->assertLessThan(4, $duration);
     }
 
+    public function testGetContentAfterDestruct()
+    {
+        $client = $this->getHttpClient(__FUNCTION__);
+
+        try {
+            $client->request('GET', 'http://localhost:8057/404');
+            $this->fail(ClientExceptionInterface::class.' expected');
+        } catch (ClientExceptionInterface $e) {
+            $this->assertSame('GET', $e->getResponse()->toArray(false)['REQUEST_METHOD']);
+        }
+    }
+
     public function testProxy()
     {
         $client = $this->getHttpClient(__FUNCTION__);