From 9bf4a24a6e3321c067c7412348b4bc62fbfc235f Mon Sep 17 00:00:00 2001 From: Laurent VOULLEMIER Date: Sun, 11 Apr 2021 11:56:19 +0200 Subject: [PATCH 01/67] [Routing] Fix localized paths --- .../Component/Routing/Annotation/Route.php | 14 ++- .../Routing/Tests/Annotation/RouteTest.php | 93 ++++++++++++------- .../AnnotationFixtures/FooController.php | 78 ++++++++++++++++ .../AttributeFixtures/FooController.php | 58 ++++++++++++ 4 files changed, 208 insertions(+), 35 deletions(-) create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/FooController.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/FooController.php diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php index 4751f14b2d84d..dda98eac70ed5 100644 --- a/src/Symfony/Component/Routing/Annotation/Route.php +++ b/src/Symfony/Component/Routing/Annotation/Route.php @@ -69,7 +69,19 @@ public function __construct( } elseif (!\is_array($data)) { throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data))); } elseif ([] !== $data) { - trigger_deprecation('symfony/routing', '5.3', 'Passing an array as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__); + $deprecation = false; + foreach ($data as $key => $val) { + if (\in_array($key, ['path', 'name', 'requirements', 'options', 'defaults', 'host', 'methods', 'schemes', 'condition', 'priority', 'locale', 'format', 'utf8', 'stateless', 'env', 'value'])) { + $deprecation = true; + } + } + + if ($deprecation) { + trigger_deprecation('symfony/routing', '5.3', 'Passing an array as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__); + } else { + $localizedPaths = $data; + $data = ['path' => $localizedPaths]; + } } if (null !== $path && !\is_string($path) && !\is_array($path)) { throw new \TypeError(sprintf('"%s": Argument $path is expected to be a string, array or null, got "%s".', __METHOD__, get_debug_type($path))); diff --git a/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php b/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php index d0c57113adab8..d5f4e19291c6f 100644 --- a/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php +++ b/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php @@ -11,73 +11,98 @@ namespace Symfony\Component\Routing\Tests\Annotation; +use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\FooController; +use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\FooController as FooAttributesController; class RouteTest extends TestCase { use ExpectDeprecationTrait; - /** - * @group legacy - */ - public function testInvalidRouteParameter() + private function getMethodAnnotation(string $method, bool $attributes): Route { - $this->expectException(\BadMethodCallException::class); - new Route(['foo' => 'bar']); + $class = $attributes ? FooAttributesController::class : FooController::class; + $reflection = new \ReflectionMethod($class, $method); + + if ($attributes) { + $attributes = $reflection->getAttributes(Route::class); + $route = $attributes[0]->newInstance(); + } else { + $reader = new AnnotationReader(); + $route = $reader->getMethodAnnotation($reflection, Route::class); + } + + if (!$route instanceof Route) { + throw new \Exception('Can\'t parse annotation'); + } + + return $route; + } + + public function provideDeprecationArrayAsFirstArgument() + { + return [ + ['requirements', ['locale' => 'en'], 'getRequirements'], + ['options', ['compiler_class' => 'RouteCompiler'], 'getOptions'], + ['name', 'blog_index', 'getName'], + ['defaults', ['_controller' => 'MyBlogBundle:Blog:index'], 'getDefaults'], + ['schemes', ['https'], 'getSchemes'], + ['methods', ['GET', 'POST'], 'getMethods'], + ['host', '{locale}.example.com', 'getHost'], + ['condition', 'context.getMethod() == "GET"', 'getCondition'], + ['value', '/Blog', 'getPath'], + ['value', ['nl' => '/hier', 'en' => '/here'], 'getLocalizedPaths'], + ]; } /** * @group legacy + * @dataProvider provideDeprecationArrayAsFirstArgument */ - public function testTryingToSetLocalesDirectly() + public function testDeprecationArrayAsFirstArgument(string $parameter, $value, string $getter) { - $this->expectException(\BadMethodCallException::class); - new Route(['locales' => ['nl' => 'bar']]); + $this->expectDeprecation('Since symfony/routing 5.3: Passing an array as first argument to "Symfony\Component\Routing\Annotation\Route::__construct" is deprecated. Use named arguments instead.'); + + $route = new Route([$parameter => $value]); + $this->assertEquals($route->$getter(), $value); } /** * @requires PHP 8 * @dataProvider getValidParameters */ - public function testRouteParameters(string $parameter, $value, string $getter) + public function testRouteParameters(string $methodName, string $getter, $expectedReturn) { - $route = new Route(...[$parameter => $value]); - $this->assertEquals($route->$getter(), $value); + $route = $this->getMethodAnnotation($methodName, true); + $this->assertEquals($route->$getter(), $expectedReturn); } /** * @group legacy - * @dataProvider getLegacyValidParameters + * @dataProvider getValidParameters */ - public function testLegacyRouteParameters(string $parameter, $value, string $getter) + public function testLegacyRouteParameters(string $methodName, string $getter, $expectedReturn) { - $this->expectDeprecation('Since symfony/routing 5.3: Passing an array as first argument to "Symfony\Component\Routing\Annotation\Route::__construct" is deprecated. Use named arguments instead.'); - - $route = new Route([$parameter => $value]); - $this->assertEquals($route->$getter(), $value); + $route = $this->getMethodAnnotation($methodName, false); + $this->assertEquals($route->$getter(), $expectedReturn); } public function getValidParameters(): iterable { return [ - ['requirements', ['locale' => 'en'], 'getRequirements'], - ['options', ['compiler_class' => 'RouteCompiler'], 'getOptions'], - ['name', 'blog_index', 'getName'], - ['defaults', ['_controller' => 'MyBlogBundle:Blog:index'], 'getDefaults'], - ['schemes', ['https'], 'getSchemes'], - ['methods', ['GET', 'POST'], 'getMethods'], - ['host', '{locale}.example.com', 'getHost'], - ['condition', 'context.getMethod() == "GET"', 'getCondition'], + ['simplePath', 'getPath', '/Blog'], + ['localized', 'getLocalizedPaths', ['nl' => '/hier', 'en' => '/here']], + ['requirements', 'getRequirements', ['locale' => 'en']], + ['options', 'getOptions', ['compiler_class' => 'RouteCompiler']], + ['name', 'getName', 'blog_index'], + ['defaults', 'getDefaults', ['_controller' => 'MyBlogBundle:Blog:index']], + ['schemes', 'getSchemes', ['https']], + ['methods', 'getMethods', ['GET', 'POST']], + ['host', 'getHost', '{locale}.example.com'], + ['condition', 'getCondition', 'context.getMethod() == \'GET\''], ]; } - - public function getLegacyValidParameters(): iterable - { - yield from $this->getValidParameters(); - - yield ['value', '/Blog', 'getPath']; - yield ['value', ['nl' => '/hier', 'en' => '/here'], 'getLocalizedPaths']; - } } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/FooController.php b/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/FooController.php new file mode 100644 index 0000000000000..2eea313bba5a2 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/FooController.php @@ -0,0 +1,78 @@ + '/hier', 'en' => '/here'])] + public function localized() + { + } + + #[Route(requirements: ['locale' => 'en'])] + public function requirements() + { + } + + #[Route(options: ['compiler_class' => 'RouteCompiler'])] + public function options() + { + } + + #[Route(name: 'blog_index')] + public function name() + { + } + + #[Route(defaults: ['_controller' => 'MyBlogBundle:Blog:index'])] + public function defaults() + { + } + + #[Route(schemes: ['https'])] + public function schemes() + { + } + + #[Route(methods: ['GET', 'POST'])] + public function methods() + { + } + + #[Route(host: '{locale}.example.com')] + public function host() + { + } + + #[Route(condition: 'context.getMethod() == \'GET\'')] + public function condition() + { + } +} From 25813e380470d1780447e5971cfd77cdee9f48cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 23 Apr 2021 17:09:03 +0200 Subject: [PATCH 02/67] [DependencyInjection] Better exception when a configurator is not type hinted --- .../Component/DependencyInjection/Loader/PhpFileLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php index f10aaac4a16d8..9cc191481d7ee 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php @@ -102,7 +102,7 @@ private function executeCallback(callable $callback, ContainerConfigurator $cont foreach ($parameters as $parameter) { $reflectionType = $parameter->getType(); if (!$reflectionType instanceof \ReflectionNamedType) { - throw new \InvalidArgumentException(sprintf('Could not resolve argument "$%s" for "%s".', $parameter->getName(), $path)); + throw new \InvalidArgumentException(sprintf('Could not resolve argument "$%s" for "%s". You must typehint it (for example with "%s" or "%s").', $parameter->getName(), $path, ContainerConfigurator::class, ContainerBuilder::class)); } $type = $reflectionType->getName(); From b7658aac3fc48a9c7762ed2040debf3015a428f8 Mon Sep 17 00:00:00 2001 From: Maciej Zgadzaj Date: Sat, 24 Apr 2021 23:53:14 +0200 Subject: [PATCH 03/67] PhpDocExtractor: Handle "true" and "false" property types --- .../Tests/Extractor/PhpDocExtractorTest.php | 4 +++ .../Extractor/ReflectionExtractorTest.php | 8 ++++++ .../Tests/Fixtures/ParentDummy.php | 28 +++++++++++++++++++ src/Symfony/Component/PropertyInfo/Type.php | 4 +++ 4 files changed, 44 insertions(+) diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index 3acadc6484be2..1a7dd664af4b8 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -109,7 +109,11 @@ public function typesProvider() ['a', [new Type(Type::BUILTIN_TYPE_INT)], 'A.', null], ['b', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], 'B.', null], ['c', [new Type(Type::BUILTIN_TYPE_BOOL, true)], null, null], + ['ct', [new Type(Type::BUILTIN_TYPE_TRUE, true)], null, null], + ['cf', [new Type(Type::BUILTIN_TYPE_FALSE, true)], null, null], ['d', [new Type(Type::BUILTIN_TYPE_BOOL)], null, null], + ['dt', [new Type(Type::BUILTIN_TYPE_TRUE)], null, null], + ['df', [new Type(Type::BUILTIN_TYPE_FALSE)], null, null], ['e', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))], null, null], ['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))], null, null], ['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null], diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index 1f65e57b62bd9..4fad83ef83a37 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -88,7 +88,11 @@ public function testGetProperties() 'date', 'element', 'c', + 'ct', + 'cf', 'd', + 'dt', + 'df', 'e', 'f', ], @@ -134,7 +138,11 @@ public function testGetPropertiesWithCustomPrefixes() 'parentAnnotationNoParent', 'date', 'c', + 'ct', + 'cf', 'd', + 'dt', + 'df', 'e', 'f', ], diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php index d698983254d75..a7c1f513a78c7 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php @@ -65,6 +65,20 @@ public function isC() { } + /** + * @return true|null + */ + public function isCt() + { + } + + /** + * @return false|null + */ + public function isCf() + { + } + /** * @return bool */ @@ -72,6 +86,20 @@ public function canD() { } + /** + * @return true + */ + public function canDt() + { + } + + /** + * @return false + */ + public function canDf() + { + } + /** * @param resource $e */ diff --git a/src/Symfony/Component/PropertyInfo/Type.php b/src/Symfony/Component/PropertyInfo/Type.php index 582b98d6411f5..02c387db47483 100644 --- a/src/Symfony/Component/PropertyInfo/Type.php +++ b/src/Symfony/Component/PropertyInfo/Type.php @@ -24,6 +24,8 @@ class Type public const BUILTIN_TYPE_FLOAT = 'float'; public const BUILTIN_TYPE_STRING = 'string'; public const BUILTIN_TYPE_BOOL = 'bool'; + public const BUILTIN_TYPE_TRUE = 'true'; + public const BUILTIN_TYPE_FALSE = 'false'; public const BUILTIN_TYPE_RESOURCE = 'resource'; public const BUILTIN_TYPE_OBJECT = 'object'; public const BUILTIN_TYPE_ARRAY = 'array'; @@ -41,6 +43,8 @@ class Type self::BUILTIN_TYPE_FLOAT, self::BUILTIN_TYPE_STRING, self::BUILTIN_TYPE_BOOL, + self::BUILTIN_TYPE_TRUE, + self::BUILTIN_TYPE_FALSE, self::BUILTIN_TYPE_RESOURCE, self::BUILTIN_TYPE_OBJECT, self::BUILTIN_TYPE_ARRAY, From 838f36b09f9dc5763b8b2f8007547d0b8b1de9a7 Mon Sep 17 00:00:00 2001 From: Pavel Kirpitsov Date: Tue, 27 Apr 2021 13:20:43 +0300 Subject: [PATCH 04/67] [Notifier] [Bridge] Store message id for slack transport's SendMessage --- .../Notifier/Bridge/Slack/SlackTransport.php | 5 ++++- .../Bridge/Slack/Tests/SlackTransportTest.php | 12 ++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php index 5a98638ab30d5..740a4937ebd6b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php @@ -91,6 +91,9 @@ protected function doSend(MessageInterface $message): SentMessage throw new TransportException(sprintf('Unable to post the Slack message: "%s".', $result['error']), $response); } - return new SentMessage($message, (string) $this); + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($result['ts']); + + return $sentMessage; } } diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php index 3b7a3c6f8ff64..12e00e3f4a951 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php @@ -110,7 +110,7 @@ public function testSendWithOptions() $response->expects($this->once()) ->method('getContent') - ->willReturn(json_encode(['ok' => true])); + ->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247'])); $expectedBody = json_encode(['channel' => $channel, 'text' => $message]); @@ -122,7 +122,9 @@ public function testSendWithOptions() $transport = $this->createTransport($client, $channel); - $transport->send(new ChatMessage('testMessage')); + $sentMessage = $transport->send(new ChatMessage('testMessage')); + + $this->assertSame('1503435956.000247', $sentMessage->getMessageId()); } public function testSendWithNotification() @@ -138,7 +140,7 @@ public function testSendWithNotification() $response->expects($this->once()) ->method('getContent') - ->willReturn(json_encode(['ok' => true])); + ->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247'])); $notification = new Notification($message); $chatMessage = ChatMessage::fromNotification($notification); @@ -158,7 +160,9 @@ public function testSendWithNotification() $transport = $this->createTransport($client, $channel); - $transport->send($chatMessage); + $sentMessage = $transport->send($chatMessage); + + $this->assertSame('1503435956.000247', $sentMessage->getMessageId()); } public function testSendWithInvalidOptions() From 92a61b1cf60a90470d620a7f04377dd7e9d9d9f8 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Fri, 23 Apr 2021 18:47:46 +0200 Subject: [PATCH 05/67] [Translation] Set default locale for IdentityTranslatorTest --- .../Component/Translation/Tests/IdentityTranslatorTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Translation/Tests/IdentityTranslatorTest.php b/src/Symfony/Component/Translation/Tests/IdentityTranslatorTest.php index a630a7a3ce4f0..16945d38a293a 100644 --- a/src/Symfony/Component/Translation/Tests/IdentityTranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/IdentityTranslatorTest.php @@ -23,6 +23,7 @@ protected function setUp(): void parent::setUp(); $this->defaultLocale = \Locale::getDefault(); + \Locale::setDefault('en'); } protected function tearDown(): void From a942b5f6849810d45aab163c40381a16d5e8dbd9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 28 Apr 2021 13:42:03 +0200 Subject: [PATCH 06/67] Avoid regenerating the remember me token if it is still fresh --- .../Http/RememberMe/PersistentRememberMeHandler.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php b/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php index 03d41a3950505..952b78cb56418 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php +++ b/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php @@ -74,8 +74,12 @@ public function processRememberMe(RememberMeDetails $rememberMeDetails, UserInte throw new AuthenticationException('The cookie has expired.'); } - $tokenValue = base64_encode(random_bytes(64)); - $this->tokenProvider->updateToken($series, $this->generateHash($tokenValue), new \DateTime()); + // if a token was regenerated less than a minute ago, there is no need to regenerate it + // if multiple concurrent requests reauthenticate a user we do not want to update the token several times + if ($persistentToken->getLastUsed()->getTimestamp() + 60 < time()) { + $tokenValue = base64_encode(random_bytes(64)); + $this->tokenProvider->updateToken($series, $this->generateHash($tokenValue), new \DateTime()); + } $this->createCookie($rememberMeDetails->withValue($tokenValue)); } From a95bbaaaef1f706836a59f021ac42cd5af576609 Mon Sep 17 00:00:00 2001 From: Roman Anasal Date: Wed, 28 Apr 2021 22:58:08 +0200 Subject: [PATCH 07/67] [TwigBridge] Fix HTML for translatable custom-file label in Bootstrap 4 theme Bootstraps allows to translate the label of the button of a custom-file input with SCSS variables depending on the lang attribute of the *input*. This attribute was set on the label instead and thus had no effect. --- .../Twig/Resources/views/Form/bootstrap_4_layout.html.twig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig index c990d81370f3c..b279477265bf8 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig @@ -121,11 +121,12 @@ {% block file_widget -%} <{{ element|default('div') }} class="custom-file"> {%- set type = type|default('file') -%} - {{- block('form_widget_simple') -}} - {%- set label_attr = label_attr|merge({ class: (label_attr.class|default('') ~ ' custom-file-label')|trim }) -%} {%- set input_lang = 'en' -%} {% if app is defined and app.request is defined %}{%- set input_lang = app.request.locale -%}{%- endif -%} -