diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 84dd98f43ad2b..62662f876fd3a 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,6 +1,6 @@
| Q | A
| ------------- | ---
-| Branch? | 5.4 for features / 4.4, 5.2 or 5.3 for bug fixes
+| Branch? | 5.4 for features / 4.4 or 5.3 for bug fixes
| Bug fix? | yes/no
| New feature? | yes/no
| Deprecations? | yes/no
diff --git a/.github/patch-types.php b/.github/patch-types.php
index 33ba6347a3ef0..2b633c9d99557 100644
--- a/.github/patch-types.php
+++ b/.github/patch-types.php
@@ -32,6 +32,7 @@
case false !== strpos($file, '/src/Symfony/Component/ErrorHandler/Tests/Fixtures/'):
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php'):
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php'):
+ case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php81Dummy.php'):
case false !== strpos($file, '/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php'):
case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'):
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'):
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index acdb9ff52b6f4..19e3da080c93b 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -180,3 +180,13 @@ jobs:
# docker run --rm -e COMPOSER_ROOT_VERSION -v $(pwd):/app -v $(which composer):/usr/local/bin/composer -v $(which vulcain):/usr/local/bin/vulcain -w /app php:8.0-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push
# sudo rm -rf .phpunit
# [ -d .phpunit.bak ] && mv .phpunit.bak .phpunit
+
+ - uses: marceloprado/has-changed-path@v1
+ id: changed-translation-files
+ with:
+ paths: src/**/Resources/translations/*.xlf
+
+ - name: Check Translation Status
+ if: steps.changed-translation-files.outputs.changed == 'true'
+ run: |
+ php src/Symfony/Component/Translation/Resources/bin/translation-status.php -v
diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml
index a9adb8e7cf532..9c126e2ef2422 100644
--- a/.github/workflows/psalm.yml
+++ b/.github/workflows/psalm.yml
@@ -52,4 +52,5 @@ jobs:
- name: Psalm
run: |
+ ./vendor/bin/psalm.phar --no-progress
./vendor/bin/psalm.phar --output-format=github --no-progress
diff --git a/CHANGELOG-5.2.md b/CHANGELOG-5.2.md
index b3ee27e04730b..d5799da0cdfae 100644
--- a/CHANGELOG-5.2.md
+++ b/CHANGELOG-5.2.md
@@ -7,6 +7,12 @@ in 5.2 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.2.0...v5.2.1
+* 5.2.14 (2021-07-29)
+
+ * bug #42307 [Mailer] Fixed decode exception when sendgrid response is 202 (rubanooo)
+ * bug #42296 [Dotenv][Yaml] Remove PHP 8.0 polyfill (derrabus)
+ * bug #42289 [HttpFoundation] Fixed type mismatch (Toflar)
+
* 5.2.13 (2021-07-27)
* bug #42270 [WebProfilerBundle] [WebProfiler] "empty" filter bugfix. Filter with name "empty" is not … (luzrain)
diff --git a/CHANGELOG-5.3.md b/CHANGELOG-5.3.md
index 76537968a1c3c..c949725e03b9b 100644
--- a/CHANGELOG-5.3.md
+++ b/CHANGELOG-5.3.md
@@ -7,6 +7,42 @@ in 5.3 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.3.0...v5.3.1
+* 5.3.7 (2021-08-30)
+
+ * bug #42769 [HttpClient] Don't pass float to `usleep()` (derrabus)
+ * bug #42753 Cast ini_get to an integer to match expected type (natewiebe13)
+ * bug #42345 [Messenger] Remove indices in messenger table on MySQL to prevent deadlocks while removing messages when running multiple consumers (jeroennoten)
+ * bug #41378 [Messenger] Fix `ErrorDetailsStamp` denormalization (wucdbm)
+ * bug #42160 [Translation] Extract translatable content on twig set (natewiebe13)
+ * bug #42053 [Notifier] [Smsapi] fixed checking whether message is sent (damlox)
+ * bug #40744 allow null for framework.translator.default_path (SimonHeimberg)
+ * bug #39856 [DomCrawler] improve failure messages of the CrawlerSelectorTextContains constraint (xabbuh)
+ * bug #40545 [HttpFoundation] Fix isNotModified determination logic (ol0lll)
+ * bug #42368 [FrameworkBundle] Fall back to default configuration in debug:config and consistently resolve parameter values (herndlm)
+ * bug #41684 Fix Url Validator false positives (sidz)
+ * bug #42576 [Translation] Reverse fallback locales (ro0NL)
+ * bug #42721 Escape all special characters for parse_mode MARKDOWN_V2 (thomas2411)
+ * bug #42707 [Messenger] [AMQP] Do not leak any credentials when connection fails (ruudk)
+ * bug #42690 [Notifier] Firebase error handling (mishaklomp)
+ * bug #42628 [PropertyInfo] Support for the `never` return type (derrabus)
+ * bug #42685 Fix ProgressBar to correctly clear multi-line formats (rtek)
+ * bug #42649 [Translation] Fix message key handling for the Localise provider (xepozz)
+ * bug #42659 Ignoring X-Transport header while signing email with DKIM (metaer)
+ * bug #42585 [ExpressionLanguage] [Lexer] Remove PHP 8.0 polyfill (nigelmann)
+ * bug #42621 [Security] Don't produce TypeErrors for non-string CSRF tokens (derrabus)
+ * bug #42596 [Security] Fix wrong cache directive when using the new PUBLIC_ACCESS attribute (wouterj)
+ * bug #42445 [Cache] fix wiring async cache recomputing in debug mode (nicolas-grekas)
+ * bug #42365 [Cache] Do not add namespace argument to `NullAdapter` in `CachePoolPass` (olsavmic)
+ * bug #42331 [HttpKernel] always close open stopwatch section after handling `kernel.request` events (xabbuh)
+ * bug #42381 [Console] Don't pass null to preg_replace() (derrabus)
+ * bug #42347 Fix ServiceLocator indexing when service is decorated (shyim)
+ * bug #42380 [HttpFoundation] Don't pass null to strpos() (derrabus)
+ * bug #42377 [HttpKernel] Remove preloading legacy event dispatcher (OskarStark)
+ * bug #42260 Fix return types for PHP 8.1 (derrabus)
+ * bug #42360 [DoctrineBridge] Typehint against doctrine/persistence interfaces (malarzm)
+ * bug #42341 [Validator] Update MIR card scheme (ossinkine)
+ * bug #42321 [PasswordHasher] Fix usage of PasswordHasherAdapter in PasswordHasherFactory (peter17)
+
* 5.3.6 (2021-07-29)
* bug #42307 [Mailer] Fixed decode exception when sendgrid response is 202 (rubanooo)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 8e58e8b338eb2..5c9b27862671f 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -139,6 +139,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Włodzimierz Gajda (gajdaw)
- Christian Scheb
- Adrien Brault (adrienbrault)
+ - Yanick Witschi (toflar)
- Julien Falque (julienfalque)
- Jacob Dreesen (jdreesen)
- Malte Schlüter (maltemaltesich)
@@ -148,13 +149,12 @@ The Symfony Connect username in parenthesis allows to get more information
- Colin Frei
- Javier Spagnoletti (phansys)
- Joshua Thijssen
- - Yanick Witschi (toflar)
+ - HypeMC (hypemc)
- Daniel Wehner (dawehner)
- Tugdual Saunier (tucksaun)
- excelwebzone
- Gordon Franke (gimler)
- Saif Eddin Gmati (azjezz)
- - HypeMC (hypemc)
- Jesse Rushlow (geeshoe)
- Fabien Pennequin (fabienpennequin)
- Théo FIDRY (theofidry)
@@ -163,10 +163,12 @@ The Symfony Connect username in parenthesis allows to get more information
- Eric GELOEN (gelo)
- Matthieu Napoli (mnapoli)
- Jannik Zschiesche (apfelbox)
+ - Mathieu Santostefano (welcomattic)
- Robert Schönthal (digitalkaoz)
- Florian Lonqueu-Brochard (florianlb)
- Richard van Laak (rvanlaak)
- Tigran Azatyan (tigranazatyan)
+ - YaFou
- Gary PEGEOT (gary-p)
- Gabriel Caruso (carusogabriel)
- Stefano Sala (stefano.sala)
@@ -184,12 +186,10 @@ The Symfony Connect username in parenthesis allows to get more information
- Albert Casademont (acasademont)
- Arnaud Kleinpeter (nanocom)
- Guilherme Blanco (guilhermeblanco)
- - Mathieu Santostefano (welcomattic)
- Smaine Milianni (ismail1432)
- SpacePossum
- Pablo Godel (pgodel)
- Andreas Braun
- - YaFou
- Jérémie Augustin (jaugustin)
- François-Xavier de Guillebon (de-gui_f)
- Oleg Voronkovich
@@ -291,6 +291,7 @@ The Symfony Connect username in parenthesis allows to get more information
- sun (sun)
- Larry Garfield (crell)
- Edi Modrić (emodric)
+ - Roman Martinuk (a2a4)
- Leo Feyer (leofeyer)
- Nikolay Labinskiy (e-moe)
- Martin Schuhfuß (usefulthink)
@@ -330,7 +331,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Pascal Montoya
- Julien Brochet (mewt)
- Gocha Ossinkine (ossinkine)
- - Roman Martinuk (a2a4)
- Tristan Darricau (nicofuma)
- Victor Bocharsky (bocharsky_bw)
- Bozhidar Hristov (warxcell)
@@ -453,6 +453,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Serkan Yildiz (srknyldz)
- Andrew Moore (finewolf)
- Bertrand Zuchuat (garfield-fr)
+ - Sébastien Alfaiate (seb33300)
- Gabor Toth (tgabi333)
- realmfoo
- Thomas Tourlourat (armetiz)
@@ -556,7 +557,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Loïc Chardonnet (gnusat)
- Marek Kalnik (marekkalnik)
- Vyacheslav Salakhutdinov (megazoll)
- - Sébastien Alfaiate (seb33300)
- Phil Taylor (prazgod)
- Hassan Amouhzi
- Tamas Szijarto
@@ -632,6 +632,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Gunnstein Lye (glye)
- Matthias Krauser (mkrauser)
- Erkhembayar Gantulga (erheme318)
+ - Lorenzo Millucci (lmillucci)
- Jérôme Tamarelle (jtamarelle-prismamedia)
- Andrii Popov (andrii-popov)
- Islam93
@@ -887,7 +888,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Julien Turby
- Marvin Butkereit
- Eduard Bulava (nonanerz)
- - Lorenzo Millucci (lmillucci)
- Renan
- Ricky Su (ricky)
- Igor Timoshenko (igor.timoshenko)
@@ -1418,6 +1418,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Valérian Galliat
- d-ph
- Renan Taranto (renan-taranto)
+ - Adrien Chinour
- Rikijs Murgs
- Uladzimir Tsykun
- Amaury Leroux de Lens (amo__)
@@ -1710,6 +1711,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Nicolas Fabre (nfabre)
- Raul Rodriguez (raul782)
- Piet Steinhart
+ - mousezheng
- mshavliuk
- Rémy LESCALLIER
- WybrenKoelmans
@@ -2099,6 +2101,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Andrei C. (moldman)
- Povilas S. (povilas)
- Laurent Negre (raulnet)
+ - Sergey Fokin (tyraelqp)
- Evrard Boulou
- pborreli
- Boris Betzholz
@@ -2796,6 +2799,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Benedict Massolle (bemas)
- Gerard Berengue Llobera (bere)
- Bernd Matzner (bmatzner)
+ - Anton (bonio)
- Bram Tweedegolf (bram_tweedegolf)
- Brandon Kelly (brandonkelly)
- Choong Wei Tjeng (choonge)
diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php
index a8825e75e915f..01dce37978c94 100644
--- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php
+++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php
@@ -117,7 +117,7 @@ public function createNewToken(PersistentTokenInterface $token)
$sql = 'INSERT INTO rememberme_token (class, username, series, value, lastUsed) VALUES (:class, :username, :series, :value, :lastUsed)';
$paramValues = [
'class' => $token->getClass(),
- // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0
+ // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
'username' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(),
'series' => $token->getSeries(),
'value' => $token->getTokenValue(),
diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php
index 20264f4d954dd..b9bdf26e0d1ca 100644
--- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php
+++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php
@@ -66,7 +66,7 @@ public function loadUserByIdentifier(string $identifier): UserInterface
throw new \InvalidArgumentException(sprintf('You must either make the "%s" entity Doctrine Repository ("%s") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration.', $this->classOrAlias, get_debug_type($repository)));
}
- // @deprecated since 5.3, change to $repository->loadUserByIdentifier() in 6.0
+ // @deprecated since Symfony 5.3, change to $repository->loadUserByIdentifier() in 6.0
if (method_exists($repository, 'loadUserByIdentifier')) {
$user = $repository->loadUserByIdentifier($identifier);
} else {
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php
index 9d794b430cdbb..ab35078f85fca 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php
+++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php
@@ -865,6 +865,10 @@ public function resultWithEmptyIterator(): array
return [
[$entity, new class() implements \Iterator {
+ /**
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
public function current()
{
return null;
@@ -875,19 +879,28 @@ public function valid(): bool
return false;
}
- public function next()
+ public function next(): void
{
}
+ /**
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
public function key()
{
+ return false;
}
- public function rewind()
+ public function rewind(): void
{
}
}],
[$entity, new class() implements \Iterator {
+ /**
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
public function current()
{
return false;
@@ -898,15 +911,20 @@ public function valid(): bool
return false;
}
- public function next()
+ public function next(): void
{
}
+ /**
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
public function key()
{
+ return false;
}
- public function rewind()
+ public function rewind(): void
{
}
}],
diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php
index 2cf9ca8c0d074..4996848143b73 100644
--- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php
+++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php
@@ -11,9 +11,9 @@
namespace Symfony\Bridge\Doctrine\Validator\Constraints;
-use Doctrine\ORM\EntityManagerInterface;
-use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Persistence\ManagerRegistry;
+use Doctrine\Persistence\Mapping\ClassMetadata;
+use Doctrine\Persistence\ObjectManager;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
@@ -177,7 +177,7 @@ public function validate($entity, Constraint $constraint)
->addViolation();
}
- private function formatWithIdentifiers(EntityManagerInterface $em, ClassMetadata $class, $value)
+ private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, $value)
{
if (!\is_object($value) || $value instanceof \DateTimeInterface) {
return $this->formatValue($value, self::PRETTY_DATE);
diff --git a/src/Symfony/Bridge/Monolog/Processor/AbstractTokenProcessor.php b/src/Symfony/Bridge/Monolog/Processor/AbstractTokenProcessor.php
index 15919978857c3..1f37d88aea4e2 100644
--- a/src/Symfony/Bridge/Monolog/Processor/AbstractTokenProcessor.php
+++ b/src/Symfony/Bridge/Monolog/Processor/AbstractTokenProcessor.php
@@ -46,7 +46,7 @@ public function __invoke(array $record): array
'roles' => $token->getRoleNames(),
];
- // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0
+ // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
if (method_exists($token, 'getUserIdentifier')) {
$record['extra'][$this->getKey()]['username'] = $record['extra'][$this->getKey()]['user_identifier'] = $token->getUserIdentifier();
} else {
diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
index 63d7562a1d5ad..c92b47f3c0236 100644
--- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
+++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
@@ -224,10 +224,10 @@
@copy("$PHPUNIT_VERSION_DIR/phpunit.xsd", 'phpunit.xsd');
chdir("$PHPUNIT_VERSION_DIR");
if ($SYMFONY_PHPUNIT_REMOVE) {
- $passthruOrFail("$COMPOSER remove --no-update ".$SYMFONY_PHPUNIT_REMOVE);
+ $passthruOrFail("$COMPOSER remove --no-update --no-interaction ".$SYMFONY_PHPUNIT_REMOVE);
}
if ($SYMFONY_PHPUNIT_REQUIRE) {
- $passthruOrFail("$COMPOSER require --no-update ".$SYMFONY_PHPUNIT_REQUIRE);
+ $passthruOrFail("$COMPOSER require --no-update --no-interaction ".$SYMFONY_PHPUNIT_REQUIRE);
}
if (5.1 <= $PHPUNIT_VERSION && $PHPUNIT_VERSION < 5.4) {
$passthruOrFail("$COMPOSER require --no-update phpunit/phpunit-mock-objects \"~3.1.0\"");
diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php
index e5b0fc4ea1b73..d42245e2b89a4 100644
--- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php
+++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php
@@ -69,12 +69,10 @@ protected function doEnterNode(Node $node, Environment $env): Node
$this->getReadDomainFromArguments($node->getNode('arguments'), 1),
];
} elseif (
- $node instanceof FilterExpression &&
- 'trans' === $node->getNode('filter')->getAttribute('value') &&
- $node->getNode('node') instanceof FunctionExpression &&
- 't' === $node->getNode('node')->getAttribute('name')
+ $node instanceof FunctionExpression &&
+ 't' === $node->getAttribute('name')
) {
- $nodeArguments = $node->getNode('node')->getNode('arguments');
+ $nodeArguments = $node->getNode('arguments');
if ($nodeArguments->getIterator()->current() instanceof ConstantExpression) {
$this->messages[] = [
diff --git a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php
index 6a7336d7b1995..8013714d7b40c 100644
--- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php
@@ -66,6 +66,7 @@ public function getExtractData()
['{% set foo = "new key" | trans %}', ['new key' => 'messages']],
['{{ 1 ? "new key" | trans : "another key" | trans }}', ['new key' => 'messages', 'another key' => 'messages']],
['{{ t("new key") | trans() }}', ['new key' => 'messages']],
+ ['{% set foo = t("new key") %}', ['new key' => 'messages']],
['{{ t("new key", {}, "domain") | trans() }}', ['new key' => 'domain']],
['{{ 1 ? t("new key") | trans : t("another key") | trans }}', ['new key' => 'messages', 'another key' => 'messages']],
diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
index a44777adbdf87..b0d5565398d2d 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
@@ -61,7 +61,7 @@ protected function configure()
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
-The %command.name% command clears the application cache for a given environment
+The %command.name% command clears and warms up the application cache for a given environment
and debug mode:
php %command.full_name% --env=dev
diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php
index 8aad13098e1bd..e88de6e31f257 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php
@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -90,22 +91,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
$extension = $this->findExtension($name);
- $container = $this->compileContainer();
-
$extensionAlias = $extension->getAlias();
- $extensionConfig = [];
- foreach ($container->getCompilerPassConfig()->getPasses() as $pass) {
- if ($pass instanceof ValidateEnvPlaceholdersPass) {
- $extensionConfig = $pass->getExtensionConfig();
- break;
- }
- }
-
- if (!isset($extensionConfig[$extensionAlias])) {
- throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias));
- }
+ $container = $this->compileContainer();
- $config = $container->resolveEnvPlaceholders($extensionConfig[$extensionAlias]);
+ $config = $container->resolveEnvPlaceholders(
+ $container->getParameterBag()->resolveValue(
+ $this->getConfigForExtension($extension, $container)
+ )
+ );
if (null === $path = $input->getArgument('path')) {
$io->title(
@@ -166,4 +159,33 @@ private function getConfigForPath(array $config, string $path, string $alias)
return $config;
}
+
+ private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container): array
+ {
+ $extensionAlias = $extension->getAlias();
+
+ $extensionConfig = [];
+ foreach ($container->getCompilerPassConfig()->getPasses() as $pass) {
+ if ($pass instanceof ValidateEnvPlaceholdersPass) {
+ $extensionConfig = $pass->getExtensionConfig();
+ break;
+ }
+ }
+
+ if (isset($extensionConfig[$extensionAlias])) {
+ return $extensionConfig[$extensionAlias];
+ }
+
+ // Fall back to default config if the extension has one
+
+ if (!$extension instanceof ConfigurationExtensionInterface) {
+ throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias));
+ }
+
+ $configs = $container->getExtensionConfig($extensionAlias);
+ $configuration = $extension->getConfiguration($configs, $container);
+ $this->validateConfiguration($extension, $configuration);
+
+ return (new Processor())->processConfiguration($configuration, $configs);
+ }
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index e16295293fcd5..669a5a9ff5dcb 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -1278,7 +1278,9 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
$container->getDefinition('console.command.translation_update')->replaceArgument(6, $transPaths);
}
- if ($container->fileExists($defaultDir)) {
+ if (null === $defaultDir) {
+ // allow null
+ } elseif ($container->fileExists($defaultDir)) {
$dirs[] = $defaultDir;
} else {
$nonExistingDirs[] = $defaultDir;
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DefaultConfigTestBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DefaultConfigTestBundle.php
new file mode 100644
index 0000000000000..8c7a89574729f
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DefaultConfigTestBundle.php
@@ -0,0 +1,9 @@
+getRootNode()
+ ->children()
+ ->scalarNode('foo')->defaultValue('%default_config_test_foo%')->end()
+ ->scalarNode('baz')->defaultValue('%env(BAZ)%')->end()
+ ->end();
+
+ return $treeBuilder;
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php
new file mode 100644
index 0000000000000..d380bcaad17fa
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php
@@ -0,0 +1,18 @@
+processConfiguration($configuration, $configs);
+
+ $container->setParameter('default_config_test', $config['foo']);
+ $container->setParameter('default_config_test', $config['baz']);
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php
new file mode 100644
index 0000000000000..79f0a7006c89b
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php
@@ -0,0 +1,28 @@
+assertStringContainsString('secret: test', $tester->getDisplay());
}
+ public function testDefaultParameterValueIsResolvedIfConfigIsExisting()
+ {
+ $tester = $this->createCommandTester();
+ $ret = $tester->execute(['name' => 'framework']);
+
+ $this->assertSame(0, $ret, 'Returns 0 in case of success');
+ $kernelCacheDir = $this->application->getKernel()->getContainer()->getParameter('kernel.cache_dir');
+ $this->assertStringContainsString(sprintf("dsn: 'file:%s/profiler'", $kernelCacheDir), $tester->getDisplay());
+ }
+
public function testDumpUndefinedBundleOption()
{
$tester = $this->createCommandTester();
@@ -74,6 +84,33 @@ public function testDumpWithPrefixedEnv()
$this->assertStringContainsString("cookie_httponly: '%env(bool:COOKIE_HTTPONLY)%'", $tester->getDisplay());
}
+ public function testDumpFallsBackToDefaultConfigAndResolvesParameterValue()
+ {
+ $tester = $this->createCommandTester();
+ $ret = $tester->execute(['name' => 'DefaultConfigTestBundle']);
+
+ $this->assertSame(0, $ret, 'Returns 0 in case of success');
+ $this->assertStringContainsString('foo: bar', $tester->getDisplay());
+ }
+
+ public function testDumpFallsBackToDefaultConfigAndResolvesEnvPlaceholder()
+ {
+ $tester = $this->createCommandTester();
+ $ret = $tester->execute(['name' => 'DefaultConfigTestBundle']);
+
+ $this->assertSame(0, $ret, 'Returns 0 in case of success');
+ $this->assertStringContainsString("baz: '%env(BAZ)%'", $tester->getDisplay());
+ }
+
+ public function testDumpThrowsExceptionWhenDefaultConfigFallbackIsImpossible()
+ {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The extension with alias "extension_without_config_test" does not have configuration.');
+
+ $tester = $this->createCommandTester();
+ $tester->execute(['name' => 'ExtensionWithoutConfigTestBundle']);
+ }
+
private function createCommandTester(): CommandTester
{
$command = $this->application->find('debug:config');
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/bundles.php
index 15ff182c6fed5..c4fb0bbe8ce48 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/bundles.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/bundles.php
@@ -10,9 +10,13 @@
*/
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
+use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\DefaultConfigTestBundle\DefaultConfigTestBundle;
+use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\ExtensionWithoutConfigTestBundle\ExtensionWithoutConfigTestBundle;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle;
return [
+ new DefaultConfigTestBundle(),
+ new ExtensionWithoutConfigTestBundle(),
new FrameworkBundle(),
new TestBundle(),
];
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml
index 1ec484a7f5208..9d7765d5e583e 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml
@@ -12,3 +12,4 @@ parameters:
env(LOCALE): en
env(COOKIE_HTTPONLY): '1'
secret: test
+ default_config_test_foo: bar
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php
index 0e1ed19e414df..f6098ea6e8eca 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php
@@ -199,7 +199,7 @@ public function testAssertSelectorTextNotContains()
{
$this->getCrawlerTester(new Crawler('
Foo'))->assertSelectorTextNotContains('body > h1', 'Bar');
$this->expectException(AssertionFailedError::class);
- $this->expectExceptionMessage('matches selector "body > h1" and does not have a node matching selector "body > h1" with content containing "Foo".');
+ $this->expectExceptionMessage('matches selector "body > h1" and the text "Foo" of the node matching selector "body > h1" does not contain "Foo".');
$this->getCrawlerTester(new Crawler('Foo'))->assertSelectorTextNotContains('body > h1', 'Foo');
}
@@ -215,7 +215,7 @@ public function testAssertPageTitleContains()
{
$this->getCrawlerTester(new Crawler('Foobar'))->assertPageTitleContains('Foo');
$this->expectException(AssertionFailedError::class);
- $this->expectExceptionMessage('matches selector "title" and has a node matching selector "title" with content containing "Bar".');
+ $this->expectExceptionMessage('matches selector "title" and the text "Foo" of the node matching selector "title" contains "Bar".');
$this->getCrawlerTester(new Crawler('Foo'))->assertPageTitleContains('Bar');
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index f27bfd8afcc72..460249f72a2c3 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -40,7 +40,7 @@
"symfony/browser-kit": "^4.4|^5.0",
"symfony/console": "^5.2",
"symfony/css-selector": "^4.4|^5.0",
- "symfony/dom-crawler": "^4.4|^5.0",
+ "symfony/dom-crawler": "^4.4.30|^5.3.7",
"symfony/dotenv": "^5.1",
"symfony/polyfill-intl-icu": "~1.0",
"symfony/form": "^5.2",
diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php
index 45447f50e82b5..e6456dd05b4da 100644
--- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php
+++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php
@@ -100,7 +100,7 @@ public function collect(Request $request, Response $response, \Throwable $except
$impersonatorUser = null;
if ($token instanceof SwitchUserToken) {
$originalToken = $token->getOriginalToken();
- // @deprecated since 5.3, change to $originalToken->getUserIdentifier() in 6.0
+ // @deprecated since Symfony 5.3, change to $originalToken->getUserIdentifier() in 6.0
$impersonatorUser = method_exists($originalToken, 'getUserIdentifier') ? $originalToken->getUserIdentifier() : $originalToken->getUsername();
}
@@ -130,7 +130,7 @@ public function collect(Request $request, Response $response, \Throwable $except
'token' => $token,
'token_class' => $this->hasVarDumper ? new ClassStub(\get_class($token)) : \get_class($token),
'logout_url' => $logoutUrl,
- // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0
+ // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
'user' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(),
'roles' => $assignedRoles,
'inherited_roles' => array_unique($inheritedRoles),
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/UserChangingUserProvider.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/UserChangingUserProvider.php
index e7206f4020726..a5306b6bf1607 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/UserChangingUserProvider.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/UserChangingUserProvider.php
@@ -11,6 +11,7 @@
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\RememberMeBundle\Security;
+use Symfony\Component\Security\Core\User\InMemoryUser;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
@@ -39,7 +40,7 @@ public function refreshUser(UserInterface $user)
{
$user = $this->inner->refreshUser($user);
- $alterUser = \Closure::bind(function (User $user) { $user->password = 'foo'; }, null, User::class);
+ $alterUser = \Closure::bind(function (InMemoryUser $user) { $user->password = 'foo'; }, null, class_exists(User::class) ? User::class : InMemoryUser::class);
$alterUser($user);
return $user;
diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
index 1146d1b7e98e2..0cfd10ae86be8 100644
--- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
@@ -52,7 +52,7 @@ static function ($key, $value, $isHit) {
// Detect wrapped values that encode for their expiry and creation duration
// For compactness, these values are packed in the key of an array using
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
- if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
+ if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
$item->value = $v[$k];
$v = unpack('Ve/Nc', substr($k, 1, -1));
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
diff --git a/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php b/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php
index 90266c78f7251..6fda0ef37f262 100644
--- a/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php
@@ -122,7 +122,7 @@ protected function doSave(array $values, int $lifetime)
} catch (\Throwable $e) {
if (1 === \count($values)) {
// Workaround https://github.com/krakjoe/apcu/issues/170
- apcu_delete(key($values));
+ apcu_delete(array_key_first($values));
}
throw $e;
diff --git a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php b/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php
index 436880ff3785a..80211a84236bc 100644
--- a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php
@@ -25,7 +25,6 @@ class MemcachedAdapter extends AbstractAdapter
/**
* We are replacing characters that are illegal in Memcached keys with reserved characters from
* {@see \Symfony\Contracts\Cache\ItemInterface::RESERVED_CHARACTERS} that are legal in Memcached.
-This conversation was marked as resolved by lstrojny
* Note: don’t use {@see \Symfony\Component\Cache\Adapter\AbstractAdapter::NS_SEPARATOR}.
*/
private const RESERVED_MEMCACHED = " \n\r\t\v\f\0";
diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php
index fdf48a7442b99..7291a7e48f6e0 100644
--- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php
@@ -63,7 +63,7 @@ static function ($key, $innerItem, $poolHash) {
// Detect wrapped values that encode for their expiry and creation duration
// For compactness, these values are packed in the key of an array using
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
- if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
+ if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
$item->value = $v[$k];
$v = unpack('Ve/Nc', substr($k, 1, -1));
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
diff --git a/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php b/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php
index 0bc7bc78e0ca7..843232e441530 100644
--- a/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php
+++ b/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php
@@ -72,6 +72,15 @@ private function addToCollector(string $id, string $name, ContainerBuilder $cont
}
$recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]);
+ foreach ($definition->getMethodCalls() as [$method, $args]) {
+ if ('setCallbackWrapper' !== $method || !$args[0] instanceof Definition || !($args[0]->getArguments()[2] ?? null) instanceof Definition) {
+ continue;
+ }
+ if ([new Reference($id), 'setCallbackWrapper'] == $args[0]->getArguments()[2]->getFactory()) {
+ $args[0]->getArguments()[2]->setFactory([new Reference($innerId), 'setCallbackWrapper']);
+ }
+ }
+
$definition->setTags([]);
$definition->setPublic(false);
diff --git a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php
index 107734c3ecd17..c5f64745ac62e 100644
--- a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php
+++ b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php
@@ -14,6 +14,7 @@
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\ChainAdapter;
+use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Component\Cache\Adapter\ParameterNormalizer;
use Symfony\Component\Cache\Messenger\EarlyExpirationDispatcher;
use Symfony\Component\DependencyInjection\ChildDefinition;
@@ -144,7 +145,7 @@ public function process(ContainerBuilder $container)
$chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider'])));
}
- if (isset($tags[0]['namespace']) && ArrayAdapter::class !== $adapter->getClass()) {
+ if (isset($tags[0]['namespace']) && !\in_array($adapter->getClass(), [ArrayAdapter::class, NullAdapter::class], true)) {
$chainedPool->replaceArgument($i++, $tags[0]['namespace']);
}
@@ -180,7 +181,7 @@ public function process(ContainerBuilder $container)
),
]);
$pool->addTag($this->reversibleTag);
- } elseif ('namespace' !== $attr || ArrayAdapter::class !== $class) {
+ } elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class], true)) {
$argument = $tags[0][$attr];
if ('default_lifetime' === $attr && !is_numeric($argument)) {
diff --git a/src/Symfony/Component/Cache/Tests/DependencyInjection/CacheCollectorPassTest.php b/src/Symfony/Component/Cache/Tests/DependencyInjection/CacheCollectorPassTest.php
index d0ea55313b885..8ea6950429faa 100644
--- a/src/Symfony/Component/Cache/Tests/DependencyInjection/CacheCollectorPassTest.php
+++ b/src/Symfony/Component/Cache/Tests/DependencyInjection/CacheCollectorPassTest.php
@@ -22,6 +22,7 @@
use Symfony\Component\Cache\Tests\Fixtures\ArrayCache;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class CacheCollectorPassTest extends TestCase
@@ -31,6 +32,13 @@ public function testProcess()
$container = new ContainerBuilder();
$container
->register('fs', FilesystemAdapter::class)
+ ->addMethodCall('setCallbackWrapper', [(new Definition())
+ ->addArgument(null)
+ ->addArgument(null)
+ ->addArgument((new Definition('callable'))
+ ->setFactory([new Reference('fs'), 'setCallbackWrapper'])
+ ),
+ ])
->addTag('cache.pool');
$container
->register('tagged_fs', TagAwareAdapter::class)
@@ -60,6 +68,9 @@ public function testProcess()
$this->assertSame(TagAwareAdapter::class, $container->getDefinition('php')->getClass());
$this->assertFalse($collector->isPublic(), 'The "data_collector.cache" should be private after processing');
+
+ $innerFs = $container->getDefinition('fs.recorder_inner');
+ $this->assertEquals([new Reference('fs.recorder_inner'), 'setCallbackWrapper'], $innerFs->getMethodCalls()[0][1][0]->getArgument(2)->getFactory());
}
public function testProcessCacheObjectsAreDecorated()
diff --git a/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php b/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php
index 5ce26b9d7f660..39350274aea33 100644
--- a/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php
+++ b/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php
@@ -15,6 +15,7 @@
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\ChainAdapter;
+use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
use Symfony\Component\DependencyInjection\ChildDefinition;
@@ -116,6 +117,23 @@ public function testNamespaceArgumentIsNotReplacedIfArrayAdapterIsUsed()
$this->assertCount(0, $container->getDefinition('app.cache_pool')->getArguments());
}
+ public function testNamespaceArgumentIsNotReplacedIfNullAdapterIsUsed()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('kernel.container_class', 'app');
+ $container->setParameter('kernel.project_dir', 'foo');
+
+ $container->register('cache.adapter.null', NullAdapter::class);
+
+ $cachePool = new ChildDefinition('cache.adapter.null');
+ $cachePool->addTag('cache.pool');
+ $container->setDefinition('app.cache_pool', $cachePool);
+
+ $this->cachePoolPass->process($container);
+
+ $this->assertCount(0, $container->getDefinition('app.cache_pool')->getArguments());
+ }
+
public function testArgsAreReplaced()
{
$container = new ContainerBuilder();
diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json
index d680ef51bcaaf..207f311595570 100644
--- a/src/Symfony/Component/Cache/composer.json
+++ b/src/Symfony/Component/Cache/composer.json
@@ -26,6 +26,7 @@
"psr/log": "^1.1|^2|^3",
"symfony/cache-contracts": "^1.1.7|^2",
"symfony/deprecation-contracts": "^2.1",
+ "symfony/polyfill-php73": "^1.9",
"symfony/polyfill-php80": "^1.16",
"symfony/service-contracts": "^1.1|^2",
"symfony/var-exporter": "^4.4|^5.0"
diff --git a/src/Symfony/Component/Console/Event/ConsoleEvent.php b/src/Symfony/Component/Console/Event/ConsoleEvent.php
index 89ab645594ce1..4e00f7c73d6d4 100644
--- a/src/Symfony/Component/Console/Event/ConsoleEvent.php
+++ b/src/Symfony/Component/Console/Event/ConsoleEvent.php
@@ -28,7 +28,7 @@ class ConsoleEvent extends Event
private $input;
private $output;
- public function __construct(Command $command = null, InputInterface $input, OutputInterface $output)
+ public function __construct(?Command $command, InputInterface $input, OutputInterface $output)
{
$this->command = $command;
$this->input = $input;
diff --git a/src/Symfony/Component/Console/Helper/Helper.php b/src/Symfony/Component/Console/Helper/Helper.php
index 881b4dc4fb4a1..cfcbbd9a125f5 100644
--- a/src/Symfony/Component/Console/Helper/Helper.php
+++ b/src/Symfony/Component/Console/Helper/Helper.php
@@ -42,7 +42,7 @@ public function getHelperSet()
/**
* Returns the length of a string, using mb_strwidth if it is available.
*
- * @deprecated since 5.3
+ * @deprecated since Symfony 5.3
*
* @return int The length of the string
*/
@@ -154,7 +154,7 @@ public static function formatMemory(int $memory)
}
/**
- * @deprecated since 5.3
+ * @deprecated since Symfony 5.3
*/
public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, ?string $string)
{
@@ -170,7 +170,7 @@ public static function removeDecoration(OutputFormatterInterface $formatter, ?st
// remove <...> formatting
$string = $formatter->format($string ?? '');
// remove already formatted characters
- $string = preg_replace("/\033\[[^m]*m/", '', $string);
+ $string = preg_replace("/\033\[[^m]*m/", '', $string ?? '');
$formatter->setDecorated($isDecorated);
return $string;
diff --git a/src/Symfony/Component/Console/Helper/HelperSet.php b/src/Symfony/Component/Console/Helper/HelperSet.php
index 5c08a7606d289..679dceab16921 100644
--- a/src/Symfony/Component/Console/Helper/HelperSet.php
+++ b/src/Symfony/Component/Console/Helper/HelperSet.php
@@ -89,8 +89,9 @@ public function getCommand()
}
/**
- * @return Helper[]
+ * @return \Traversable
*/
+ #[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator($this->helpers);
diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php
index 91fba2b587364..1c03a1d968afc 100644
--- a/src/Symfony/Component/Console/Helper/ProgressBar.php
+++ b/src/Symfony/Component/Console/Helper/ProgressBar.php
@@ -482,8 +482,10 @@ private function overwrite(string $message): void
}
$this->output->clear($lineCount);
} else {
- if ($this->formatLineCount > 0) {
- $this->cursor->moveUp($this->formatLineCount);
+ for ($i = 0; $i < $this->formatLineCount; ++$i) {
+ $this->cursor->moveToColumn(1);
+ $this->cursor->clearLine();
+ $this->cursor->moveUp();
}
$this->cursor->moveToColumn(1);
diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
index e424a29dfa4a0..ef5f0622269db 100644
--- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
@@ -812,7 +812,7 @@ public function testMultilineFormat()
$this->assertEquals(
">---------------------------\nfoobar".
$this->generateOutput("=========>------------------\nfoobar").
- "\x1B[1A\x1B[1G\x1B[2K".
+ "\x1B[1G\x1B[2K\x1B[1A\x1B[1G\x1B[2K".
$this->generateOutput("============================\nfoobar"),
stream_get_contents($output->getStream())
);
@@ -983,7 +983,7 @@ protected function generateOutput($expected)
{
$count = substr_count($expected, "\n");
- return ($count ? sprintf("\x1B[%dA\x1B[1G\x1b[2K", $count) : "\x1B[1G\x1B[2K").$expected;
+ return ($count ? str_repeat("\x1B[1G\x1b[2K\x1B[1A", $count) : '')."\x1B[1G\x1B[2K".$expected;
}
public function testBarWidthWithMultilineFormat()
@@ -1095,4 +1095,33 @@ public function testNoWriteWhenMessageIsSame()
stream_get_contents($output->getStream())
);
}
+
+ public function testMultiLineFormatIsFullyCleared()
+ {
+ $bar = new ProgressBar($output = $this->getOutputStream(), 3);
+ $bar->setFormat("%current%/%max%\n%message%\nFoo");
+
+ $bar->setMessage('1234567890');
+ $bar->start();
+ $bar->display();
+
+ $bar->setMessage('ABC');
+ $bar->advance();
+ $bar->display();
+
+ $bar->setMessage('A');
+ $bar->advance();
+ $bar->display();
+
+ $bar->finish();
+
+ rewind($output->getStream());
+ $this->assertEquals(
+ "0/3\n1234567890\nFoo".
+ $this->generateOutput("1/3\nABC\nFoo").
+ $this->generateOutput("2/3\nA\nFoo").
+ $this->generateOutput("3/3\nA\nFoo"),
+ stream_get_contents($output->getStream())
+ );
+ }
}
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php
index 12d3b26f740df..47f51c2478940 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php
@@ -62,10 +62,10 @@ public function __construct()
new AutowireRequiredMethodsPass(),
new AutowireRequiredPropertiesPass(),
new ResolveBindingsPass(),
- new DecoratorServicePass(),
new CheckDefinitionValidityPass(),
new AutowirePass(false),
new ServiceLocatorTagPass(),
+ new DecoratorServicePass(),
new ResolveTaggedIteratorArgumentPass(),
new ResolveServiceSubscribersPass(),
new ResolveReferencesToAliasesPass(),
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php
index 25063d35ff3b5..24088fe0f504a 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php
@@ -13,8 +13,11 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
+use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
@@ -143,4 +146,56 @@ public function testBindingsAreCopied()
$this->assertSame(['foo'], array_keys($locator->getBindings()));
$this->assertInstanceOf(BoundArgument::class, $locator->getBindings()['foo']);
}
+
+ public function testIndexedByServiceIdWithDecoration()
+ {
+ $container = new ContainerBuilder();
+
+ $locator = new Definition(Locator::class);
+ $locator->setPublic(true);
+ $locator->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('test_tag', null, null, true)));
+
+ $container->setDefinition(Locator::class, $locator);
+
+ $service = new Definition(Service::class);
+ $service->setPublic(true);
+ $service->addTag('test_tag');
+
+ $container->setDefinition(Service::class, $service);
+
+ $decorated = new Definition(Decorated::class);
+ $decorated->setPublic(true);
+ $decorated->setDecoratedService(Service::class);
+
+ $container->setDefinition(Decorated::class, $decorated);
+
+ $container->compile();
+
+ /** @var ServiceLocator $locator */
+ $locator = $container->get(Locator::class)->locator;
+ static::assertTrue($locator->has(Service::class));
+ static::assertFalse($locator->has(Decorated::class));
+ static::assertInstanceOf(Decorated::class, $locator->get(Service::class));
+ }
+}
+
+class Locator
+{
+ /**
+ * @var ServiceLocator
+ */
+ public $locator;
+
+ public function __construct(ServiceLocator $locator)
+ {
+ $this->locator = $locator;
+ }
+}
+
+class Service
+{
+}
+
+class DecoratedService
+{
}
diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php
index 6c61f7e677b29..19ead5213a37a 100644
--- a/src/Symfony/Component/DomCrawler/Crawler.php
+++ b/src/Symfony/Component/DomCrawler/Crawler.php
@@ -1112,6 +1112,7 @@ public function getNode(int $position)
/**
* @return int
*/
+ #[\ReturnTypeWillChange]
public function count()
{
return \count($this->nodes);
@@ -1120,6 +1121,7 @@ public function count()
/**
* @return \ArrayIterator|\DOMNode[]
*/
+ #[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator($this->nodes);
diff --git a/src/Symfony/Component/DomCrawler/Form.php b/src/Symfony/Component/DomCrawler/Form.php
index 2e9e01dc65dfe..baa81209902d4 100644
--- a/src/Symfony/Component/DomCrawler/Form.php
+++ b/src/Symfony/Component/DomCrawler/Form.php
@@ -315,6 +315,7 @@ public function all()
*
* @return bool true if the field exists, false otherwise
*/
+ #[\ReturnTypeWillChange]
public function offsetExists($name)
{
return $this->has($name);
@@ -329,6 +330,7 @@ public function offsetExists($name)
*
* @throws \InvalidArgumentException if the field does not exist
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($name)
{
return $this->fields->get($name);
@@ -344,6 +346,7 @@ public function offsetGet($name)
*
* @throws \InvalidArgumentException if the field does not exist
*/
+ #[\ReturnTypeWillChange]
public function offsetSet($name, $value)
{
$this->fields->set($name, $value);
@@ -356,6 +359,7 @@ public function offsetSet($name, $value)
*
* @return void
*/
+ #[\ReturnTypeWillChange]
public function offsetUnset($name)
{
$this->fields->remove($name);
diff --git a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php
index 7008779e14203..a198784d448df 100644
--- a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php
+++ b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php
@@ -47,7 +47,7 @@ protected function matches($crawler): bool
return false;
}
- return $this->expectedText === trim($crawler->attr($this->attribute));
+ return $this->expectedText === trim($crawler->attr($this->attribute) ?? '');
}
/**
diff --git a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorTextContains.php b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorTextContains.php
index ffc6f5677cc5d..c4697ecf75376 100644
--- a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorTextContains.php
+++ b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorTextContains.php
@@ -18,6 +18,8 @@ final class CrawlerSelectorTextContains extends Constraint
{
private $selector;
private $expectedText;
+ private $hasNode = false;
+ private $nodeText;
public function __construct(string $selector, string $expectedText)
{
@@ -30,7 +32,11 @@ public function __construct(string $selector, string $expectedText)
*/
public function toString(): string
{
- return sprintf('has a node matching selector "%s" with content containing "%s"', $this->selector, $this->expectedText);
+ if ($this->hasNode) {
+ return sprintf('the text "%s" of the node matching selector "%s" contains "%s"', $this->nodeText, $this->selector, $this->expectedText);
+ }
+
+ return sprintf('the Crawler has a node matching selector "%s"', $this->selector);
}
/**
@@ -42,10 +48,15 @@ protected function matches($crawler): bool
{
$crawler = $crawler->filter($this->selector);
if (!\count($crawler)) {
+ $this->hasNode = false;
+
return false;
}
- return false !== mb_strpos($crawler->text(null, true), $this->expectedText);
+ $this->hasNode = true;
+ $this->nodeText = $crawler->text(null, true);
+
+ return false !== mb_strpos($this->nodeText, $this->expectedText);
}
/**
@@ -55,6 +66,6 @@ protected function matches($crawler): bool
*/
protected function failureDescription($crawler): string
{
- return 'the Crawler '.$this->toString();
+ return $this->toString();
}
}
diff --git a/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorAttributeValueSameTest.php b/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorAttributeValueSameTest.php
index ab528ab356443..47ecdc8a04438 100644
--- a/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorAttributeValueSameTest.php
+++ b/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorAttributeValueSameTest.php
@@ -23,6 +23,7 @@ public function testConstraint()
{
$constraint = new CrawlerSelectorAttributeValueSame('input[name="username"]', 'value', 'Fabien');
$this->assertTrue($constraint->evaluate(new Crawler('