diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
index e59b6670f0..3cc7f5fa0e 100644
--- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
+++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
@@ -148,8 +148,8 @@ if ('disabled' === $getEnvVar('SYMFONY_DEPRECATIONS_HELPER')) {
}
$COMPOSER = file_exists($COMPOSER = $oldPwd.'/composer.phar')
- || ($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`))
+ || ($COMPOSER = rtrim('\\' === \DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar 2> NUL`) : `which composer.phar 2> /dev/null`))
+ || ($COMPOSER = rtrim('\\' === \DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer 2> NUL`) : `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')
? ('#!/usr/bin/env php' === file_get_contents($COMPOSER, false, null, 0, 18) ? $PHP : '').' '.escapeshellarg($COMPOSER) // detect shell wrappers by looking at the shebang
: 'composer';
@@ -176,9 +176,9 @@ if (!file_exists("$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit") || $configurationH
@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 2> NUL' : 'rm -rf %s', escapeshellarg("$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', escapeshellarg("$PHPUNIT_VERSION_DIR.old")));
}
$info = [];
@@ -307,10 +307,15 @@ EOPHP
// This is useful for static analytics tools such as PHPStan having to load PHPUnit's classes
// and for other testing libraries such as Behat using PHPUnit's assertions.
chdir($PHPUNIT_DIR);
-if (file_exists('phpunit')) {
- @unlink('phpunit');
+if ('\\' === \DIRECTORY_SEPARATOR) {
+ passthru('rmdir /S /Q phpunit 2> NUL');
+ passthru(sprintf('mklink /j phpunit %s > NUL 2>&1', escapeshellarg($PHPUNIT_VERSION_DIR)));
+} else {
+ if (file_exists('phpunit')) {
+ @unlink('phpunit');
+ }
+ @symlink($PHPUNIT_VERSION_DIR, 'phpunit');
}
-@symlink($PHPUNIT_VERSION_DIR, 'phpunit');
chdir($oldPwd);
if ($PHPUNIT_VERSION < 8.0) {
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 32c7f7e5cc..37d8d64b90 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -2109,6 +2109,12 @@ class FrameworkExtension extends Extension
->setPublic($pool['public'])
;
+ if (method_exists(TagAwareAdapter::class, 'setLogger')) {
+ $container
+ ->getDefinition($name)
+ ->addMethodCall('setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]);
+ }
+
$pool['name'] = $tagAwareId = $name;
$pool['public'] = false;
$name = '.'.$name.'.inner';
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php
index 44e0c450f4..a060c13f93 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/cache.php
@@ -35,6 +35,11 @@ $container->loadFromExtension('framework', [
'redis://foo' => 'cache.adapter.redis',
],
],
+ 'cache.ccc' => [
+ 'adapter' => 'cache.adapter.array',
+ 'default_lifetime' => 410,
+ 'tags' => true,
+ ],
'cache.redis_tag_aware.foo' => [
'adapter' => 'cache.adapter.redis_tag_aware',
],
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml
index 7f04adc965..2750715f6b 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/cache.xml
@@ -18,6 +18,7 @@
*/
-class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface
+class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface
{
public const TAGS_PREFIX = "\0tags\0";
use ContractsTrait;
+ use LoggerAwareTrait;
use ProxyTrait;
private $deferred = [];
diff --git a/src/Symfony/Component/Cache/LockRegistry.php b/src/Symfony/Component/Cache/LockRegistry.php
index d8929bebd3..38c0a6cea7 100644
--- a/src/Symfony/Component/Cache/LockRegistry.php
+++ b/src/Symfony/Component/Cache/LockRegistry.php
@@ -82,7 +82,7 @@ final class LockRegistry
public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null)
{
- $key = self::$files ? crc32($item->getKey()) % \count(self::$files) : -1;
+ $key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1;
if ($key < 0 || (self::$lockedFiles[$key] ?? false) || !$lock = self::open($key)) {
return $callback($item, $save);
diff --git a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php
index e96db881d0..9a45adaa36 100644
--- a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php
+++ b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php
@@ -14,6 +14,7 @@ namespace Symfony\Component\Cache\Tests\Adapter;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
+use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
@@ -197,6 +198,20 @@ class TagAwareAdapterTest extends AdapterTestCase
$this->assertFalse($item->isHit());
}
+ public function testLog()
+ {
+ $logger = $this->createMock(LoggerInterface::class);
+ $logger
+ ->expects($this->atLeastOnce())
+ ->method($this->anything());
+
+ $cache = new TagAwareAdapter(new ArrayAdapter());
+ $cache->setLogger($logger);
+
+ // Computing will produce at least one log
+ $cache->get('foo', static function (): string { return 'ccc'; });
+ }
+
/**
* @return MockObject|PruneableCacheInterface
*/
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
index 6d73fa67f2..c2899bbd78 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
@@ -187,13 +187,14 @@ class ChoiceType extends AbstractType
}
if ($options['multiple']) {
- $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$unknownValues) {
+ $messageTemplate = $options['invalid_message'] ?? 'The value {{ value }} is not valid.';
+
+ $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$unknownValues, $messageTemplate) {
// Throw exception if unknown values were submitted
if (\count($unknownValues) > 0) {
$form = $event->getForm();
- $clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : \gettype($form->getViewData());
- $messageTemplate = 'The value {{ value }} is not valid.';
+ $clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : (\is_array($form->getViewData()) ? implode('", "', array_keys($unknownValues)) : \gettype($form->getViewData()));
if (null !== $this->translator) {
$message = $this->translator->trans($messageTemplate, ['{{ value }}' => $clientDataAsString], 'validators');
@@ -201,7 +202,7 @@ class ChoiceType extends AbstractType
$message = strtr($messageTemplate, ['{{ value }}' => $clientDataAsString]);
}
- $form->addError(new FormError($message, $messageTemplate, ['{{ value }}' => $clientDataAsString], null, new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', implode('", "', array_keys($unknownValues))))));
+ $form->addError(new FormError($message, $messageTemplate, ['{{ value }}' => $clientDataAsString], null, new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', $clientDataAsString))));
}
});
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php
index 4cf9937f04..50b259f58e 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php
@@ -1861,6 +1861,32 @@ class ChoiceTypeTest extends BaseTypeTest
$this->assertSame('name[]', $view->vars['full_name']);
}
+ public function testInvalidMessageAwarenessForMultiple()
+ {
+ $form = $this->factory->create(static::TESTED_TYPE, null, [
+ 'multiple' => true,
+ 'expanded' => false,
+ 'choices' => $this->choices,
+ 'invalid_message' => 'You are not able to use value "{{ value }}"',
+ ]);
+
+ $form->submit(['My invalid choice']);
+ $this->assertEquals("ERROR: You are not able to use value \"My invalid choice\"\n", (string) $form->getErrors(true));
+ }
+
+ public function testInvalidMessageAwarenessForMultipleWithoutScalarOrArrayViewData()
+ {
+ $form = $this->factory->create(static::TESTED_TYPE, null, [
+ 'multiple' => true,
+ 'expanded' => false,
+ 'choices' => $this->choices,
+ 'invalid_message' => 'You are not able to use value "{{ value }}"',
+ ]);
+
+ $form->submit(new \stdClass());
+ $this->assertEquals("ERROR: You are not able to use value \"stdClass\"\n", (string) $form->getErrors(true));
+ }
+
// https://github.com/symfony/symfony/issues/3298
public function testInitializeWithEmptyChoices()
{
diff --git a/src/Symfony/Component/HttpClient/Response/HttplugPromise.php b/src/Symfony/Component/HttpClient/Response/HttplugPromise.php
index 2231464a22..fbef8e29ca 100644
--- a/src/Symfony/Component/HttpClient/Response/HttplugPromise.php
+++ b/src/Symfony/Component/HttpClient/Response/HttplugPromise.php
@@ -53,10 +53,8 @@ final class HttplugPromise implements HttplugPromiseInterface
/**
* {@inheritdoc}
- *
- * @return Psr7ResponseInterface|mixed
*/
- public function wait($unwrap = true)
+ public function wait($unwrap = true): ?Psr7ResponseInterface
{
$result = $this->promise->wait($unwrap);
diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php
index 9b8891f79e..eae5fb6bab 100644
--- a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php
+++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php
@@ -56,14 +56,13 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
return;
}
- $session = null;
$request = $event->getRequest();
if (!$request->hasSession()) {
$sess = null;
$request->setSessionFactory(function () use (&$sess) { return $sess ?? $sess = $this->getSession(); });
}
- $session = $session ?? ($this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : null);
+ $session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : null;
$this->sessionUsageStack[] = $session instanceof Session ? $session->getUsageIndex() : 0;
}
diff --git a/src/Symfony/Component/Routing/Route.php b/src/Symfony/Component/Routing/Route.php
index ce52a5696f..d52ed4240a 100644
--- a/src/Symfony/Component/Routing/Route.php
+++ b/src/Symfony/Component/Routing/Route.php
@@ -539,15 +539,15 @@ class Route implements \Serializable
return $pattern;
}
- return preg_replace_callback('#\{(!?\w++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
- if (isset($m[3][0])) {
- $this->setDefault($m[1], '?' !== $m[3] ? substr($m[3], 1) : null);
+ return preg_replace_callback('#\{(!?)(\w++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
+ if (isset($m[4][0])) {
+ $this->setDefault($m[2], '?' !== $m[4] ? substr($m[4], 1) : null);
}
- if (isset($m[2][0])) {
- $this->setRequirement($m[1], substr($m[2], 1, -1));
+ if (isset($m[3][0])) {
+ $this->setRequirement($m[2], substr($m[3], 1, -1));
}
- return '{'.$m[1].'}';
+ return '{'.$m[1].$m[2].'}';
}, $pattern);
}
diff --git a/src/Symfony/Component/Routing/Tests/RouteTest.php b/src/Symfony/Component/Routing/Tests/RouteTest.php
index 5dc12c8759..63ae8d952a 100644
--- a/src/Symfony/Component/Routing/Tests/RouteTest.php
+++ b/src/Symfony/Component/Routing/Tests/RouteTest.php
@@ -50,6 +50,14 @@ class RouteTest extends TestCase
$this->assertEquals($route, $route->setPath(''), '->setPath() implements a fluent interface');
$route->setPath('//path');
$this->assertEquals('/path', $route->getPath(), '->setPath() does not allow two slashes "//" at the beginning of the path as it would be confused with a network path when generating the path from the route');
+ $route->setPath('/path/{!foo}');
+ $this->assertEquals('/path/{!foo}', $route->getPath(), '->setPath() keeps ! to pass important params');
+ $route->setPath('/path/{bar<\w++>}');
+ $this->assertEquals('/path/{bar}', $route->getPath(), '->setPath() removes inline requirements');
+ $route->setPath('/path/{foo?value}');
+ $this->assertEquals('/path/{foo}', $route->getPath(), '->setPath() removes inline defaults');
+ $route->setPath('/path/{!bar<\d+>?value}');
+ $this->assertEquals('/path/{!bar}', $route->getPath(), '->setPath() removes all inline settings');
}
public function testOptions()
@@ -221,17 +229,20 @@ class RouteTest extends TestCase
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', null), new Route('/foo/{bar?}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', 'baz'), new Route('/foo/{bar?baz}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', 'baz