8000 From a9ec12aaa0901137b6795be07002865bc9ac2f56 Mon Sep 17 00:00:00 2001 From: Nommyde Date: Mon, 31 Oct 2022 04:22:35 +0400 Subject: [PATCH 001/475] [Messenger] Improve DX --- .../Component/Messenger/Command/FailedMessagesRetryCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php b/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php index df007540c4e2b..77295b6a64370 100644 --- a/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php +++ b/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php @@ -176,7 +176,7 @@ private function runWorker(string $failureTransportName, ReceiverInterface $rece throw new \RuntimeException(sprintf('The message with id "%s" could not decoded, it can only be shown or removed.', $this->getMessageId($envelope) ?? '?')); } - $shouldHandle = $shouldForce || $io->confirm('Do you want to retry (yes) or delete this message (no)?'); + $shouldHandle = $shouldForce || 'retry' === $io->choice('Please select an action', ['retry', 'delete'], 'retry'); if ($shouldHandle) { return; From d2b6b49b766bd089e7b411cef42a52bf007ea843 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 3 Nov 2022 16:56:46 +0100 Subject: [PATCH 002/475] [HttpFoundation][Validator] Leverage json_validate() --- composer.json | 1 + .../RequestMatcher/IsJsonRequestMatcher.php | 8 +------- src/Symfony/Component/HttpFoundation/composer.json | 3 ++- .../Component/Validator/Constraints/JsonValidator.php | 4 +--- src/Symfony/Component/Validator/composer.json | 1 + 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index eda77e963e5ef..ea78be5f30023 100644 --- a/composer.json +++ b/composer.json @@ -51,6 +51,7 @@ "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php83": "^1.27", "symfony/polyfill-uuid": "^1.15" }, "replace": { diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/IsJsonRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/IsJsonRequestMatcher.php index 5da46840f4fd1..875f992be156a 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher/IsJsonRequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/IsJsonRequestMatcher.php @@ -23,12 +23,6 @@ class IsJsonRequestMatcher implements RequestMatcherInterface { public function matches(Request $request): bool { - try { - json_decode($request->getContent(), true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); - } catch (\JsonException) { - return false; - } - - return true; + return json_validate($request->getContent()); } } diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json index e333a23b7ab31..ddcb502515b74 100644 --- a/src/Symfony/Component/HttpFoundation/composer.json +++ b/src/Symfony/Component/HttpFoundation/composer.json @@ -18,7 +18,8 @@ "require": { "php": ">=8.1", "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.1" + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" }, "require-dev": { "predis/predis": "~1.0", diff --git a/src/Symfony/Component/Validator/Constraints/JsonValidator.php b/src/Symfony/Component/Validator/Constraints/JsonValidator.php index 32807a12ec59c..dd7a47eb3ef05 100644 --- a/src/Symfony/Component/Validator/Constraints/JsonValidator.php +++ b/src/Symfony/Component/Validator/Constraints/JsonValidator.php @@ -36,9 +36,7 @@ public function validate(mixed $value, Constraint $constraint) $value = (string) $value; - json_decode($value); - - if (\JSON_ERROR_NONE !== json_last_error()) { + if (!json_validate($value)) { $this->context->buildViolation($constraint->message) ->setParameter('{{ value }}', $this->formatValue($value)) ->setCode(Json::INVALID_JSON_ERROR) diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index 63cb9e3a9745e..1b076c4290f55 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -20,6 +20,7 @@ "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php83": "^1.27", "symfony/translation-contracts": "^1.1|^2|^3" }, "require-dev": { From f95f1f977075531e3981bcfd4dee9efe29bedfa8 Mon Sep 17 00:00:00 2001 From: Yannick Ihmels Date: Tue, 8 Nov 2022 21:58:02 +0100 Subject: [PATCH 003/475] Add encoder option for saving options --- src/Symfony/Component/Serializer/CHANGELOG.md | 5 +++++ .../Component/Serializer/Encoder/XmlEncoder.php | 11 +++++++++-- .../Serializer/Tests/Encoder/XmlEncoderTest.php | 4 +++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 3342ada2fea86..d7799bb50236f 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add `XmlEncoder::SAVE_OPTIONS` context option + 6.2 --- diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php index 0132f1ebf3c83..2207012ca6225 100644 --- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php @@ -44,9 +44,15 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa public const FORMAT_OUTPUT = 'xml_format_output'; /** - * A bit field of LIBXML_* constants. + * A bit field of LIBXML_* constants for loading XML documents. */ public const LOAD_OPTIONS = 'load_options'; + + /** + * A bit field of LIBXML_* constants for saving XML documents. + */ + public const SAVE_OPTIONS = 'save_options'; + public const REMOVE_EMPTY_TAGS = 'remove_empty_tags'; public const ROOT_NODE_NAME = 'xml_root_node_name'; public const STANDALONE = 'xml_standalone'; @@ -58,6 +64,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa self::DECODER_IGNORED_NODE_TYPES => [\XML_PI_NODE, \XML_COMMENT_NODE], self::ENCODER_IGNORED_NODE_TYPES => [], self::LOAD_OPTIONS => \LIBXML_NONET | \LIBXML_NOBLANKS, + self::SAVE_OPTIONS => 0, self::REMOVE_EMPTY_TAGS => false, self::ROOT_NODE_NAME => 'response', self::TYPE_CAST_ATTRIBUTES => true, @@ -88,7 +95,7 @@ public function encode(mixed $data, string $format, array $context = []): string $this->appendNode($dom, $data, $format, $context, $xmlRootNodeName); } - return $dom->saveXML($ignorePiNode ? $dom->documentElement : null); + return $dom->saveXML($ignorePiNode ? $dom->documentElement : null, $context[self::SAVE_OPTIONS] ?? $this->defaultContext[self::SAVE_OPTIONS]); } public function decode(string $data, string $format, array $context = []): mixed diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php index 77b220d585346..11e7bef3e5f17 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php @@ -189,12 +189,13 @@ public function testEncodeNotRemovingEmptyTags() public function testContext() { - $array = ['person' => ['name' => 'George Abitbol']]; + $array = ['person' => ['name' => 'George Abitbol', 'age' => null]]; $expected = <<<'XML' George Abitbol + @@ -202,6 +203,7 @@ public function testContext() $context = [ 'xml_format_output' => true, + 'save_options' => \LIBXML_NOEMPTYTAG, ]; $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); From 215e80274172d70e3c7146a878724f0ef910bee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4dlich?= Date: Sun, 6 Dec 2020 15:59:31 +0100 Subject: [PATCH 004/475] Allow to configure or disable the message bus to use --- .../DependencyInjection/Configuration.php | 3 ++ .../Resources/config/notifier.php | 19 +++++++++---- .../DependencyInjection/ConfigurationTest.php | 1 + .../notifier_with_disabled_message_bus.php | 19 +++++++++++++ .../notifier_with_specific_message_bus.php | 19 +++++++++++++ .../notifier_with_disabled_message_bus.xml | 17 +++++++++++ .../notifier_with_specific_message_bus.xml | 17 +++++++++++ .../notifier_with_disabled_message_bus.yml | 11 ++++++++ .../notifier_with_specific_message_bus.yml | 11 ++++++++ .../FrameworkExtensionTest.php | 28 +++++++++++++++++++ 10 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 698fb84fc769f..c1e567cc0e436 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -2014,6 +2014,9 @@ private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $ena ->arrayNode('notifier') ->info('Notifier configuration') ->{$enableIfStandalone('symfony/notifier', Notifier::class)}() + ->children() + ->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end() + ->end() ->fixXmlConfig('chatter_transport') ->children() ->arrayNode('chatter_transports') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php index fde0533140809..2f53ff03de03d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php @@ -52,15 +52,24 @@ ->tag('notifier.channel', ['channel' => 'browser']) ->set('notifier.channel.chat', ChatChannel::class) - ->args([service('chatter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('chatter.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'chat']) ->set('notifier.channel.sms', SmsChannel::class) - ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('texter.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'sms']) ->set('notifier.channel.email', EmailChannel::class) - ->args([service('mailer.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('mailer.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'email']) ->set('notifier.channel.push', PushChannel::class) @@ -76,7 +85,7 @@ ->set('chatter', Chatter::class) ->args([ service('chatter.transports'), - service('messenger.default_bus')->ignoreOnInvalid(), + abstract_arg('message bus'), service('event_dispatcher')->ignoreOnInvalid(), ]) @@ -96,7 +105,7 @@ ->set('texter', Texter::class) ->args([ service('texter.transports'), - service('messenger.default_bus')->ignoreOnInvalid(), + abstract_arg('message bus'), service('event_dispatcher')->ignoreOnInvalid(), ]) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 59c7bf7a8b875..f37794e3d7fe5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -655,6 +655,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor ], 'notifier' => [ 'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class), + 'message_bus' => null, 'chatter_transports' => [], 'texter_transports' => [], 'channel_policy' => [], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php new file mode 100644 index 0000000000000..014b54b94a5dd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php @@ -0,0 +1,19 @@ +loadFromExtension('framework', [ + 'messenger' => [ + 'enabled' => true, + ], + 'mailer' => [ + 'dsn' => 'smtp://example.com', + ], + 'notifier' => [ + 'message_bus' => false, + 'chatter_transports' => [ + 'test' => 'null' + ], + 'texter_transports' => [ + 'test' => 'null' + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php new file mode 100644 index 0000000000000..75074e073ce29 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php @@ -0,0 +1,19 @@ +loadFromExtension('framework', [ + 'messenger' => [ + 'enabled' => true, + ], + 'mailer' => [ + 'dsn' => 'smtp://example.com', + ], + 'notifier' => [ + 'message_bus' => 'app.another_bus', + 'chatter_transports' => [ + 'test' => 'null' + ], + 'texter_transports' => [ + 'test' => 'null' + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml new file mode 100644 index 0000000000000..599bd23cb8f43 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml @@ -0,0 +1,17 @@ + + + + + + + + + null + null + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml new file mode 100644 index 0000000000000..62373497056ac --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml @@ -0,0 +1,17 @@ + + + + + + + + + null + null + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml new file mode 100644 index 0000000000000..08b3d6ad6e759 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml @@ -0,0 +1,11 @@ +framework: + messenger: + enabled: true + mailer: + dsn: 'smtp://example.com' + notifier: + message_bus: false + chatter_transports: + test: 'null' + texter_transports: + test: 'null' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml new file mode 100644 index 0000000000000..1851717bd9627 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml @@ -0,0 +1,11 @@ +framework: + messenger: + enabled: true + mailer: + dsn: 'smtp://example.com' + notifier: + message_bus: 'app.another_bus' + chatter_transports: + test: 'null' + texter_transports: + test: 'null' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 640a152402bea..225da8671b1da 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -2173,6 +2173,34 @@ public function testHtmlSanitizerDefaultConfig() $this->assertSame('html_sanitizer', (string) $container->getAlias(HtmlSanitizerInterface::class)); } + public function testNotifierWithDisabledMessageBus() + { + $container = $this->createContainerFromFile('notifier_with_disabled_message_bus'); + + $this->assertNull($container->getDefinition('chatter')->getArgument(1)); + $this->assertNull($container->getDefinition('texter')->getArgument(1)); + $this->assertNull($container->getDefinition('notifier.channel.chat')->getArgument(1)); + $this->assertNull($container->getDefinition('notifier.channel.email')->getArgument(1)); + $this->assertNull($container->getDefinition('notifier.channel.sms')->getArgument(1)); + } + + public function testNotifierWithSpecificMessageBus() + { + $container = $this->createContainerFromFile('notifier_with_specific_message_bus'); + + $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('chatter')->getArgument(1)); + $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('texter')->getArgument(1)); + $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('notifier.channel.chat')->getArgument(1)); + $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('notifier.channel.email')->getArgument(1)); + $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('notifier.channel.sms')->getArgument(1)); + + $this->assertNull($container->getDefinition('chatter')->getArgument(0)); + $this->assertNull($container->getDefinition('texter')->getArgument(0)); + $this->assertNull($container->getDefinition('notifier.channel.chat')->getArgument(0)); + $this->assertNull($container->getDefinition('notifier.channel.email')->getArgument(0)); + $this->assertNull($container->getDefinition('notifier.channel.sms')->getArgument(0)); + } + protected function createContainer(array $data = []) { return new ContainerBuilder(new EnvPlaceholderParameterBag(array_merge([ From fc7aaa6123c3e0cf4543bb3faeb65cbde26112bc Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 21 Jul 2022 12:04:37 +0200 Subject: [PATCH 005/475] Fix logic and tests --- .../DependencyInjection/FrameworkExtension.php | 12 ++++++++++++ .../DependencyInjection/FrameworkExtensionTest.php | 6 ------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 982210c5fcc4b..6092506f9b3a2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2522,6 +2522,18 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ $container->removeDefinition('notifier.channel.email'); } + foreach (['texter', 'chatter', 'notifier.channel.chat', 'notifier.channel.email', 'notifier.channel.sms'] as $serviceId) { + if (!$container->hasDefinition($serviceId)) { + continue; + } + + if (false === $messageBus = $config['message_bus']) { + $container->getDefinition($serviceId)->replaceArgument(1, null); + } else { + $container->getDefinition($serviceId)->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE)); + } + } + if ($this->isInitializedConfigEnabled('messenger')) { if ($config['notification_on_failed_messages']) { $container->getDefinition('notifier.failed_message_listener')->addTag('kernel.event_subscriber'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 225da8671b1da..b87a877690815 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -2193,12 +2193,6 @@ public function testNotifierWithSpecificMessageBus() $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('notifier.channel.chat')->getArgument(1)); $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('notifier.channel.email')->getArgument(1)); $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('notifier.channel.sms')->getArgument(1)); - - $this->assertNull($container->getDefinition('chatter')->getArgument(0)); - $this->assertNull($container->getDefinition('texter')->getArgument(0)); - $this->assertNull($container->getDefinition('notifier.channel.chat')->getArgument(0)); - $this->assertNull($container->getDefinition('notifier.channel.email')->getArgument(0)); - $this->assertNull($container->getDefinition('notifier.channel.sms')->getArgument(0)); } protected function createContainer(array $data = []) From a9d621cca9b3c1bf019c725a140644dafbaeaa09 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 31 Oct 2022 15:48:15 +0100 Subject: [PATCH 006/475] [Notifier] Add Twitter notifier --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 + .../Notifier/Bridge/Twitter/.gitattributes | 4 + .../Notifier/Bridge/Twitter/.gitignore | 3 + .../Notifier/Bridge/Twitter/CHANGELOG.md | 7 + .../Component/Notifier/Bridge/Twitter/LICENSE | 19 ++ .../Notifier/Bridge/Twitter/README.md | 25 ++ .../Tests/TwitterTransportFactoryTest.php | 44 +++ .../Twitter/Tests/TwitterTransportTest.php | 201 +++++++++++ .../Bridge/Twitter/Tests/fixtures.gif | Bin 0 -> 185 bytes .../Bridge/Twitter/TwitterOptions.php | 189 +++++++++++ .../Bridge/Twitter/TwitterTransport.php | 311 ++++++++++++++++++ .../Twitter/TwitterTransportFactory.php | 50 +++ .../Notifier/Bridge/Twitter/composer.json | 36 ++ .../Notifier/Bridge/Twitter/phpunit.xml.dist | 31 ++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 3 + src/Symfony/Component/Notifier/Transport.php | 2 + 18 files changed, 936 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/Tests/fixtures.gif create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/TwitterOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/Twitter/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 982210c5fcc4b..a0e2d58abad65 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -172,6 +172,7 @@ use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransport; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; +use Symfony\Component\Notifier\Bridge\Twitter\TwitterTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; @@ -2593,6 +2594,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', TurboSmsTransport::class => 'notifier.transport_factory.turbo-sms', TwilioTransportFactory::class => 'notifier.transport_factory.twilio', + TwitterTransportFactory::class => 'notifier.transport_factory.twitter', VonageTransportFactory::class => 'notifier.transport_factory.vonage', YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', ZendeskTransportFactory::class => 'notifier.transport_factory.zendesk', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 237ae18a59eb7..1d2772727f782 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -59,6 +59,7 @@ use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; +use Symfony\Component\Notifier\Bridge\Twitter\TwitterTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; @@ -105,6 +106,10 @@ ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.twitter', TwitterTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + ->set('notifier.transport_factory.all-my-sms', AllMySmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Twitter/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/.gitignore b/src/Symfony/Component/Notifier/Bridge/Twitter/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Twitter/CHANGELOG.md new file mode 100644 index 0000000000000..1f2c8f86cde72 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/ 8000 Bridge/Twitter/LICENSE b/src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE new file mode 100644 index 0000000000000..0ece8964f767d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/README.md b/src/Symfony/Component/Notifier/Bridge/Twitter/README.md new file mode 100644 index 0000000000000..6a145177815e5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/README.md @@ -0,0 +1,25 @@ +Twitter Notifier +=============== + +Provides [Twitter](https://twitter.com) integration for Symfony Notifier. + +DSN example +----------- + +``` +TWITTER_DSN=twitter://API_KEY:API_SECRET:ACCESS_TOKEN:ACCESS_SECRET@default +``` + +where: + - `API_KEY` is your Twitter API Key + - `API_SECRET` is your Twitter API Key Secret + - `ACCESS_TOKEN` is your read+write Twitter Access Token + - `ACCESS_SECRET` is your read+write Twitter Access Token Secret + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportFactoryTest.php new file mode 100644 index 0000000000000..7aa6c0f2b4966 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportFactoryTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Twitter\Tests; + +use Symfony\Component\Notifier\Bridge\Twitter\TwitterTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +class TwitterTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): TwitterTransportFactory + { + return new TwitterTransportFactory(); + } + + public function createProvider(): iterable + { + yield ['twitter://host.test', 'twitter://A:B:C:D@host.test']; + } + + public function supportsProvider(): iterable + { + yield [true, 'twitter://default']; + yield [false, 'somethingElse://default']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://default']; + } + + public function incompleteDsnProvider(): iterable + { + yield ['twitter://A:B@default', 'Invalid "twitter://A:B@default" notifier DSN: Access Token is missing']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportTest.php new file mode 100644 index 0000000000000..fd79faea69601 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/TwitterTransportTest.php @@ -0,0 +1,201 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Twitter\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\Mime\Part\File; +use Symfony\Component\Notifier\Bridge\Twitter\TwitterOptions; +use Symfony\Component\Notifier\Bridge\Twitter\TwitterTransport; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +class TwitterTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null): TwitterTransport + { + return new TwitterTransport('APIK', 'APIS', 'TOKEN', 'SECRET', $client ?? $this->createMock(HttpClientInterface::class)); + } + + public function toStringProvider(): iterable + { + yield ['twitter://api.twitter.com', $this->createTransport()]; + } + + public function supportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } + + public function testBasicTweet() + { + $transport = $this->createTransport(new MockHttpClient(function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://api.twitter.com/2/tweets', $url); + $this->assertSame('{"text":"Hello World!"}', $options['body']); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"data":{"id":"abc123"}}'); + })); + + $result = $transport->send(new ChatMessage('Hello World!')); + + $this->assertSame('abc123', $result->getMessageId()); + } + + public function testTweetImage() + { + $transport = $this->createTransport(new MockHttpClient((function () { + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/upload.json?command=INIT&total_bytes=185&media_type=image%2Fgif&media_category=tweet_image', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"media_id_string":"gif123"}'); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/upload.json?command=APPEND&media_id=gif123&segment_index=0', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse(); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/upload.json?command=FINALIZE&media_id=gif123', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"processing_info":{"state":"pending","check_after_secs": 0}}'); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('GET', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=gif123', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"processing_info":{"state":"succeeded"}}'); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/metadata/create.json', $url); + $this->assertSame('{"media_id":"gif123","alt_text":{"text":"A fixture"}}', $options['body']); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"processing_info":{"state":"succeeded"}}'); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://api.twitter.com/2/tweets', $url); + $this->assertSame('{"text":"Hello World!","media":{"media_ids":["gif123"]}}', $options['body']); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"data":{"id":"abc123"}}'); + }; + })())); + + $result = $transport->send(new ChatMessage('Hello World!', (new TwitterOptions()) + ->attachImage(new File(__DIR__.'/fixtures.gif'), 'A fixture')) + ); + + $this->assertSame('abc123', $result->getMessageId()); + } + + public function testTweetVideo() + { + $transport = $this->createTransport(new MockHttpClient((function () { + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/upload.json?command=INIT&total_bytes=185&media_type=image%2Fgif&media_category=tweet_video', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"media_id_string":"gif123"}'); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/upload.json?command=INIT&total_bytes=185&media_type=image%2Fgif&media_category=subtitles', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"media_id_string":"sub234"}'); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/upload.json?command=APPEND&media_id=gif123&segment_index=0', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse(); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/upload.json?command=APPEND&media_id=sub234&segment_index=0', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse(); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/upload.json?command=FINALIZE&media_id=gif123', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{}'); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/upload.json?command=FINALIZE&media_id=sub234', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{}'); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://upload.twitter.com/1.1/media/subtitles/create.json', $url); + $this->assertSame('{"media_id":"gif123","media_category":"tweet_video","subtitle_info":{"subtitles":[{"media_id":"sub234","language_code":"en","display_name":"English"}]}}', $options['body']); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse(); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://api.twitter.com/2/tweets', $url); + $this->assertSame('{"text":"Hello World!","media":{"media_ids":["gif123"]}}', $options['body']); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"data":{"id":"abc123"}}'); + }; + })())); + + $result = $transport->send(new ChatMessage('Hello World!', (new TwitterOptions()) + ->attachVideo(new File(__DIR__.'/fixtures.gif'), '', new File(__DIR__.'/fixtures.gif', 'English.en.srt'))) + ); + + $this->assertSame('abc123', $result->getMessageId()); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/fixtures.gif b/src/Symfony/Component/Notifier/Bridge/Twitter/Tests/fixtures.gif new file mode 100644 index 0000000000000000000000000000000000000000..443aca422f7624b271903e5fbb577c7f99786c0e GIT binary patch literal 185 zcmZ?wbhEHb0d66k_ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Twitter; + +use Symfony\Component\Mime\Part\File; +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author Nicolas Grekas + */ +final class TwitterOptions implements MessageOptionsInterface +{ + public const REPLY_MENTIONED_USERS = 'mentionedUsers'; + public const REPLY_FOLLOWING = 'following'; + + + public function __construct( + private array $options = [], + ) { + } + + public function toArray(): array + { + return $this->options; + } + + public function getRecipientId(): ?string + { + return null; + } + + /** + * @param string[] $choices + * + * @return $this + */ + public function poll(array $choices, int $duration): static + { + $this->options['poll'] = [ + 'options' => $choices, + 'duration_minutes' => $duration, + ]; + + return $this; + } + + /** + * @return $this + */ + public function quote(string $tweetId): static + { + $this->options['quote_tweet_id'] = $tweetId; + + return $this; + } + + /** + * @param string[] $excludedUserIds + * + * @return $this + */ + public function inReplyTo(string $tweetId, array $excludedUserIds = []): static + { + $this->options['reply']['in_reply_to_tweet_id'] = $tweetId; + $this->options['reply']['exclude_reply_user_ids'] = $excludedUserIds; + + return $this; + } + + /** + * @param string[] $extraOwners + * + * @return $this + */ + public function attachImage(File $file, string $alt = '', array $extraOwners = []): static + { + $this->options['attach'][] = [ + 'file' => $file, + 'alt' => $alt, + 'subtitles' => null, + 'category' => 'tweet_image', + 'owners' => $extraOwners, + ]; + + return $this; + } + + /** + * @param string[] $extraOwners + * + * @return $this + */ + public function attachGif(File $file, string $alt = '', array $extraOwners = []): static + { + $this->options['attach'][] = [ + 'file' => $file, + 'alt' => $alt, + 'subtitles' => null, + 'category' => 'tweet_gif', + 'owners' => $extraOwners, + ]; + + return $this; + } + + /** + * @param File|null $subtitles File should be named as "display_name.language_code.srt" + * @param string[] $extraOwners + * + * @return $this + */ + public function attachVideo(File $file, string $alt = '', File $subtitles = null, bool $amplify = false, array $extraOwners = []): static + { + $this->options['attach'][] = [ + 'file' => $file, + 'alt' => $alt, + 'subtitles' => $subtitles, + 'category' => $amplify ? 'amplify_video' : 'tweet_video', + 'owners' => $extraOwners, + ]; + + return $this; + } + + /** + * @param self::REPLY_* $settings + * + * @return $this + */ + public function replySettings(string $settings): static + { + $this->options['reply_settings'] = $settings; + + return $this; + } + + /** + * @param string[] $userIds + * + * @return $this + */ + public function taggedUsers(array $userIds): static + { + $this->options['media']['tagged_user_ids'] = $userIds; + + return $this; + } + + /** + * @return $this + */ + public function deepLink(string $url): static + { + $this->options['direct_message_deep_link'] = $url; + + return $this; + } + + /** + * @see https://developer.twitter.com/en/docs/twitter-api/v1/geo/places-near-location/api-reference/get-geo-search + * + * @return $this + */ + public function place(string $placeId): static + { + $this->options['geo']['place_id'] = $placeId; + + return $this; + } + + /** + * @return $this + */ + public function forSuperFollowersOnly(bool $flag = true): static + { + $this->options['for_super_followers_only'] = $flag; + + return $this; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransport.php b/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransport.php new file mode 100644 index 0000000000000..b5f450b92eae5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransport.php @@ -0,0 +1,311 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Twitter; + +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\File; +use Symfony\Component\Mime\Part\Multipart\FormDataPart; +use Symfony\Component\Notifier\Exception\RuntimeException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + */ +final class TwitterTransport extends AbstractTransport +{ + protected const HOST = 'api.twitter.com'; + + private static $nonce; + + private string $apiKey; + private string $apiSecret; + private string $accessToken; + private string $accessSecret; + + public function __construct(#[\SensitiveParameter] string $apiKey, #[\SensitiveParameter] string $apiSecret, #[\SensitiveParameter] string $accessToken, #[\SensitiveParameter] string $accessSecret, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + parent::__construct($client, $dispatcher); + + $this->apiKey = $apiKey; + $this->apiSecret = $apiSecret; + $this->accessToken = $accessToken; + $this->accessSecret = $accessSecret; + } + + public function __toString(): string + { + return sprintf('twitter://%s', $this->getEndpoint()); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof ChatMessage && (null === $message->getOptions() || $message->getOptions() instanceof TwitterOptions); + } + + public function request(string $method, string $url, array $options): ResponseInterface + { + $url = 'https://'.str_replace('api.', str_starts_with($url, '/1.1/media/') ? 'upload.' : 'api.', $this->getEndpoint()).$url; + + foreach (\is_array($options['body'] ?? null) ? $options['body'] : [] as $v) { + if (!$v instanceof DataPart) { + continue; + } + + $formDataPart = new FormDataPart($options['body']); + + foreach ($formDataPart->getPreparedHeaders()->all() as $header) { + $options['headers'][] = $header->toString(); + } + + $options['body'] = $formDataPart->bodyToIterable(); + + break; + } + + $oauth = [ + 'oauth_consumer_key' => $this->apiKey, + 'oauth_nonce' => self::$nonce = md5(self::$nonce ??= random_bytes(16)), + 'oauth_signature_method' => 'HMAC-SHA1', + 'oauth_timestamp' => time(), + 'oauth_token' => $this->accessToken, + 'oauth_version' => '1.0', + ]; + + $sign = $oauth + ($options['query'] ?? []) + (\is_array($options['body'] ?? null) ? $options['body'] : []); + ksort($sign); + + $oauth['oauth_signature'] = base64_encode(hash_hmac( + 'sha1', + implode('&', array_map('rawurlencode', [ + $method, + $url, + implode('&', array_map(fn ($k) => rawurlencode($k).'='.rawurlencode($sign[$k]), array_keys($sign))), + ])), + rawurlencode($this->apiSecret).'&'.rawurlencode($this->accessSecret), + true + )); + + $options['headers'][] = 'Authorization: OAuth '.implode(', ', array_map(fn ($k) => $k.'="'.rawurlencode($oauth[$k]).'"', array_keys($oauth))); + + return $this->client->request($method, $url, $options); + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof ChatMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); + } + + $options = $message->getOptions()?->toArray() ?? []; + $options['text'] = $message->getSubject(); + $response = null; + + try { + if (isset($options['attach'])) { + $options['media']['media_ids'] = $this->uploadMedia($options['attach']); + unset($options['attach']); + } + + $response = $this->request('POST', '/2/tweets', ['json' => $options]); + $statusCode = $response->getStatusCode(); + $result = $response->toArray(false); + } catch (ExceptionInterface $e) { + if (null !== $response) { + throw new TransportException($e->getMessage(), $response, 0, $e); + } + throw new RuntimeException($e->getMessage(), 0, $e); + } + + if (400 <= $statusCode) { + throw new TransportException($result['title'].': '.($result['errors'][0]['message'] ?? $result['detail']), $response); + } + + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($result['data']['id']); + + return $sentMessage; + } + + /** + * @param array $media + */ + private function uploadMedia(array $media): array + { + $i = 0; + $pool = []; + + foreach ($media as [ + 'file' => $file, + 'alt' => $alt, + 'subtitles' => $subtitles, + 'category' => $category, + 'owners' => $extraOwners, + ]) { + $query = [ + 'command' => 'INIT', + 'total_bytes' => $file->getSize(), + 'media_type' => $file->getContentType(), + ]; + + if ($category) { + $query['media_category'] = $category; + } + + if ($extraOwners) { + $query['additional_owners'] = implode(',', $extraOwners); + } + + $pool[++$i] = $this->request('POST', '/1.1/media/upload.json', [ + 'query' => $query, + 'user_data' => [$i, null, 0, fopen($file->getPath(), 'r'), $alt, $subtitles], + ]); + + if ($subtitles) { + $query['total_bytes'] = $subtitles->getSize(); + $query['media_type'] = $subtitles->getContentType(); + $query['media_category'] = 'subtitles'; + + $pool[++$i] = $this->request('POST', '/1.1/media/upload.json', [ + 'query' => $query, + 'user_data' => [$i, null, 0, fopen($subtitles->getPath(), 'r'), null, $subtitles], + ]); + } + } + + $mediaIds = []; + $subtitlesVideoIds = []; + $subtitlesMediaIds = []; + $response = null; + + try { + while ($pool) { + foreach ($this->client->stream($pool) as $response => $chunk) { + $this->processChunk($pool, $response, $chunk, $mediaIds, $subtitlesVideoIds, $subtitlesMediaIds); + } + } + } catch (ExceptionInterface $e) { + if (null !== $response) { + throw new TransportException($e->getMessage(), $response, 0, $e); + } + throw new RuntimeException($e->getMessage(), 0, $e); + } finally { + foreach ($pool as $response) { + $response->cancel(); + } + } + + foreach (array_filter($subtitlesVideoIds) as $videoId => $subtitles) { + $name = pathinfo($subtitles->getFilename(), \PATHINFO_FILENAME); + $subtitlesVideoIds[$videoId] = $this->request('POST', '/1.1/media/subtitles/create.json', [ + 'json' => [ + 'media_id' => $videoId, + 'media_category' => 'tweet_video', + 'subtitle_info' => [ + 'subtitles' => [ + [ + 'media_id' => array_search($subtitles, $subtitlesMediaIds, true), + 'language_code' => pathinfo($name, \PATHINFO_EXTENSION), + 'display_name' => pathinfo($name, \PATHINFO_FILENAME), + ], + ], + ], + ], + ]); + } + + return $mediaIds; + } + + private function processChunk(array &$pool, ResponseInterface $response, ChunkInterface $chunk, array &$mediaIds, array &$subtitlesVideoIds, array &$subtitlesMediaIds): void + { + if ($chunk->isFirst()) { + $response->getStatusCode(); // skip non-2xx status codes + } + + if (!$chunk->isLast()) { + return; + } + + if (400 <= $response->getStatusCode()) { + $error = $response->toArray(false); + + throw new TransportException($error['errors'][0]['message'] ?? ($error['request'].': '.$error['error']), $response, $error['errors'][0]['code'] ?? 0); + } + + [$i, $mediaId, $seq, $h, $alt, $subtitles] = $response->getInfo('user_data'); + unset($pool[$i]); + + $method = 'POST'; + $options = []; + $mediaId ??= $response->toArray()['media_id_string']; + $pause = 0; + + if (0 <= $seq) { + $options['query'] = [ + 'command' => 'APPEND', + 'media_id' => $mediaId, + 'segment_index' => (string) $seq, + ]; + $options['body'] = ['media' => new DataPart(fread($h, 1024 * 1024))]; + $seq = feof($h) ? -1 : 1 + $seq; + } elseif (-1 === $seq) { + $options['query'] = ['command' => 'FINALIZE', 'media_id' => $mediaId]; + $seq = -2; + } elseif (-2 !== $seq) { + return; + } elseif ('succeeded' === $state = $response->toArray()['processing_info']['state'] ?? 'succeeded') { + if ($alt) { + $pool[$i] = $this->request('POST', '/1.1/media/metadata/create.json', [ + 'json' => [ + 'media_id' => $mediaId, + 'alt_text' => ['text' => $alt], + ], + 'user_data' => [$i, $mediaId, -3, null, null, null], + ]); + } + if (null !== $alt) { + $mediaIds[] = $mediaId; + $subtitlesVideoIds[$mediaId] = $subtitles; + } else { + $subtitlesMediaIds[$mediaId] = $subtitles; + } + + return; + } elseif ('failed' === $state) { + $error = $response->toArray()['processing_info']['error']; + + throw new TransportException($error['message'], $response, $error['code']); + } else { + $method = 'GET'; + $options['query'] = ['command' => 'STATUS', 'media_id' => $mediaId]; + $pause = $response->toArray()['processing_info']['check_after_secs']; + } + + $pool[$i] = $this->request($method, '/1.1/media/upload.json', $options + [ + 'user_data' => [$i, $mediaId, $seq, $h, $alt, $subtitles], + ]); + + if ($pause) { + ($pool[$i]->getInfo('pause_handler') ?? sleep(...))($pause); + } + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransportFactory.php new file mode 100644 index 0000000000000..1d99076dac8e7 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterTransportFactory.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Twitter; + +use Symfony\Component\Notifier\Exception\IncompleteDsnException; +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author Nicolas Grekas + */ +final class TwitterTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TwitterTransport + { + $scheme = $dsn->getScheme(); + + if ('twitter' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'twitte 10000 r', $this->getSupportedSchemes()); + } + + $apiKey = $this->getUser($dsn); + [$apiSecret, $accessToken, $accessSecret] = explode(':', $this->getPassword($dsn)) + [1 => null, null, null]; + + foreach (['API Key' => $apiKey, 'API Key Secret' => $apiSecret, 'Access Token' => $accessToken, 'Access Token Secret' => $accessSecret] as $name => $key) { + if (!$key) { + throw new IncompleteDsnException($name.' is missing.', $dsn->getOriginalDsn()); + } + } + + return (new TwitterTransport($apiKey, $apiSecret, $accessToken, $accessSecret, $this->client, $this->dispatcher)) + ->setHost('default' === $dsn->getHost() ? null : $dsn->getHost()) + ->setPort($dsn->getPort()); + } + + protected function getSupportedSchemes(): array + { + return ['twitter']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/composer.json b/src/Symfony/Component/Notifier/Bridge/Twitter/composer.json new file mode 100644 index 0000000000000..83b1a91258a0b --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/twitter-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony Twitter Notifier Bridge", + "keywords": ["twitter", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.2" + }, + "require-dev": { + "symfony/mime": "^6.2" + }, + "conflict": { + "symfony/mime": "<6.2" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Twitter\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Twitter/phpunit.xml.dist new file mode 100644 index 0000000000000..f486d936888b9 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Resources + ./Tests + ./vendor + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index f5b51bf9bfaaf..ea8783ec95428 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -204,6 +204,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Twilio\TwilioTransportFactory::class, 'package' => 'symfony/twilio-notifier', ], + 'twitter' => [ + 'class' => Bridge\Twitter\TwitterTransportFactory::class, + 'package' => 'symfony/twitter-notifier', + ], 'vonage' => [ 'class' => Bridge\Vonage\VonageTransportFactory::class, 'package' => 'symfony/vonage-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 8ffb0c1f79c4c..bcef4d5f11f13 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -56,6 +56,7 @@ use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; +use Symfony\Component\Notifier\Bridge\Twitter\TwitterTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; @@ -115,6 +116,7 @@ public static function setUpBeforeClass(): void TelnyxTransportFactory::class => false, TurboSmsTransportFactory::class => false, TwilioTransportFactory::class => false, + TwitterTransportFactory::class => false, VonageTransportFactory::class => false, YunpianTransportFactory::class => false, ZendeskTransportFactory::class => false, @@ -179,6 +181,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat yield ['telnyx', 'symfony/telnyx-notifier']; yield ['turbosms', 'symfony/turbo-sms-notifier']; yield ['twilio', 'symfony/twilio-notifier']; + yield ['twitter', 'symfony/twitter-notifier']; yield ['zendesk', 'symfony/zendesk-notifier']; yield ['zulip', 'symfony/zulip-notifier']; } diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index b9829b8ebdf68..7656194018c82 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -51,6 +51,7 @@ use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; +use Symfony\Component\Notifier\Bridge\Twitter\TwitterTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; @@ -112,6 +113,7 @@ final class Transport TelnyxTransportFactory::class, TurboSmsTransportFactory::class, TwilioTransportFactory::class, + TwitterTransportFactory::class, VonageTransportFactory::class, YunpianTransportFactory::class, ZendeskTransportFactory::class, From 047029bccbce1617b7df9c72aad9141e3166e097 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 25 Nov 2022 10:27:18 +0100 Subject: [PATCH 007/475] Bump version to 6.3 --- src/Symfony/Component/HttpKernel/Kernel.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 3661c216a2c4b..ba921fa3b31f6 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -75,15 +75,15 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.2.0-DEV'; - public const VERSION_ID = 60200; + public const VERSION = '6.3.0-DEV'; + public const VERSION_ID = 60300; public const MAJOR_VERSION = 6; - public const MINOR_VERSION = 2; + public const MINOR_VERSION = 3; public const RELEASE_VERSION = 0; public const EXTRA_VERSION = 'DEV'; - public const END_OF_MAINTENANCE = '07/2023'; - public const END_OF_LIFE = '07/2023'; + public const END_OF_MAINTENANCE = '01/2024'; + public const END_OF_LIFE = '01/2024'; public function __construct(string $environment, bool $debug) { From 1b0d16ff21844f36ab1242a447281118bfb2a36b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Nov 2022 11:21:52 +0100 Subject: [PATCH 008/475] [Contracts] update branch alias --- composer.json | 2 +- src/Symfony/Contracts/Cache/composer.json | 2 +- src/Symfony/Contracts/Deprecation/composer.json | 2 +- src/Symfony/Contracts/EventDispatcher/composer.json | 2 +- src/Symfony/Contracts/HttpClient/composer.json | 2 +- src/Symfony/Contracts/Service/composer.json | 2 +- src/Symfony/Contracts/Translation/composer.json | 2 +- src/Symfony/Contracts/composer.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index eda77e963e5ef..aef3fd1019a42 100644 --- a/composer.json +++ b/composer.json @@ -193,7 +193,7 @@ "url": "src/Symfony/Contracts", "options": { "versions": { - "symfony/contracts": "3.2.x-dev" + "symfony/contracts": "3.3.x-dev" } } }, diff --git a/src/Symfony/Contracts/Cache/composer.json b/src/Symfony/Contracts/Cache/composer.json index 27b2c84f9d912..770e20eaa8e7b 100644 --- a/src/Symfony/Contracts/Cache/composer.json +++ b/src/Symfony/Contracts/Cache/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Deprecation/composer.json b/src/Symfony/Contracts/Deprecation/composer.json index be6c494b149a6..774200fdcdae8 100644 --- a/src/Symfony/Contracts/Deprecation/composer.json +++ b/src/Symfony/Contracts/Deprecation/composer.json @@ -25,7 +25,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/EventDispatcher/composer.json b/src/Symfony/Contracts/EventDispatcher/composer.json index eecac79ca57c2..89d7cec96c7ae 100644 --- a/src/Symfony/Contracts/EventDispatcher/composer.json +++ b/src/Symfony/Contracts/EventDispatcher/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/HttpClient/composer.json b/src/Symfony/Contracts/HttpClient/composer.json index d4176ccef72ce..61eac1e2fae69 100644 --- a/src/Symfony/Contracts/HttpClient/composer.json +++ b/src/Symfony/Contracts/HttpClient/composer.json @@ -30,7 +30,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Service/composer.json b/src/Symfony/Contracts/Service/composer.json index af3559ed4983d..36b0d95e77484 100644 --- a/src/Symfony/Contracts/Service/composer.json +++ b/src/Symfony/Contracts/Service/composer.json @@ -34,7 +34,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Translation/composer.json b/src/Symfony/Contracts/Translation/composer.json index 15e4bc1b6441f..3454800a8e0bd 100644 --- a/src/Symfony/Contracts/Translation/composer.json +++ b/src/Symfony/Contracts/Translation/composer.json @@ -30,7 +30,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/composer.json b/src/Symfony/Contracts/composer.json index 63bba367386d7..031ad79a772ad 100644 --- a/src/Symfony/Contracts/composer.json +++ b/src/Symfony/Contracts/composer.json @@ -52,7 +52,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" } } } From ed94fdc2e23c5e29641da6383093906aa297ea7a Mon Sep 17 00:00:00 2001 From: Aleksey Polyvanyi Date: Tue, 8 Nov 2022 10:20:36 +0100 Subject: [PATCH 009/475] [DependencyInjection] Add `env` and `param` parameters for Autowire attribute --- .../Attribute/Autowire.php | 16 ++++++---- .../Tests/Attribute/AutowireTest.php | 29 +++++++++++++++++-- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Attribute/Autowire.php b/src/Symfony/Component/DependencyInjection/Attribute/Autowire.php index 0c34f9035160b..73e6f455d8a1b 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/Autowire.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/Autowire.php @@ -28,17 +28,21 @@ class Autowire /** * Use only ONE of the following. * - * @param string|null $value Parameter value (ie "%kernel.project_dir%/some/path") - * @param string|null $service Service ID (ie "some.service") - * @param string|null $expression Expression (ie 'service("some.service").someMethod()') + * @param string|array|null $value Parameter value (ie "%kernel.project_dir%/some/path") + * @param string|null $service Service ID (ie "some.service") + * @param string|null $expression Expression (ie 'service("some.service").someMethod()') + * @param string|null $env Environment variable name (ie 'SOME_ENV_VARIABLE') + * @param string|null $param Parameter name (ie 'some.parameter.name') */ public function __construct( string|array $value = null, string $service = null, string $expression = null, + string $env = null, + string $param = null, ) { - if (!($service xor $expression xor null !== $value)) { - throw new LogicException('#[Autowire] attribute must declare exactly one of $service, $expression, or $value.'); + if (!(null !== $value xor null !== $service xor null !== $expression xor null !== $env xor null !== $param)) { + throw new LogicException('#[Autowire] attribute must declare exactly one of $service, $expression, $env, $param or $value.'); } if (\is_string($value) && str_starts_with($value, '@')) { @@ -52,6 +56,8 @@ public function __construct( $this->value = match (true) { null !== $service => new Reference($service), null !== $expression => class_exists(Expression::class) ? new Expression($expression) : throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'), + null !== $env => "%env($env)%", + null !== $param => "%$param%", null !== $value => $value, }; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireTest.php b/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireTest.php index d45434a5817e0..fabbe3b7934c8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Attribute/AutowireTest.php @@ -19,11 +19,14 @@ class AutowireTest extends TestCase { - public function testCanOnlySetOneParameter() + /** + * @dataProvider provideMultipleParameters + */ + public function testCanOnlySetOneParameter(array $parameters) { $this->expectException(LogicException::class); - new Autowire(service: 'id', expression: 'expr'); + new Autowire(...$parameters); } public function testMustSetOneParameter() @@ -57,4 +60,26 @@ public function testCanUseValueWithAtAndEqualSign() { $this->assertInstanceOf(Expression::class, (new Autowire(value: '@=service'))->value); } + + public function testCanUseEnv() + { + $this->assertSame('%env(SOME_ENV_VAR)%', (new Autowire(env: 'SOME_ENV_VAR'))->value); + } + + public function testCanUseParam() + { + $this->assertSame('%some.param%', (new Autowire(param: 'some.param'))->value); + } + + /** + * @see testCanOnlySetOneParameter + */ + private function provideMultipleParameters(): iterable + { + yield [['service' => 'id', 'expression' => 'expr']]; + + yield [['env' => 'ENV', 'param' => 'param']]; + + yield [['value' => 'some-value', 'expression' => 'expr']]; + } } From ff76d2653d18353707ba327b21d547e9b142ab34 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sat, 29 Oct 2022 17:09:21 +0200 Subject: [PATCH 010/475] [SecurityBundle] Deprecate enabling bundle and not configuring it --- UPGRADE-6.3.md | 7 +++++++ src/Symfony/Bundle/SecurityBundle/CHANGELOG.md | 5 +++++ .../DependencyInjection/SecurityExtension.php | 4 ++++ .../SecurityExtensionTest.php | 16 ++++++++++++++++ .../Functional/app/AliasedEvents/config.yml | 8 ++++++++ 5 files changed, 40 insertions(+) create mode 100644 UPGRADE-6.3.md diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md new file mode 100644 index 0000000000000..e1655bfa81b62 --- /dev/null +++ b/UPGRADE-6.3.md @@ -0,0 +1,7 @@ +UPGRADE FROM 6.2 to 6.3 +======================= + +SecurityBundle +-------------- + + * Deprecate enabling bundle and not configuring it diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index dcefe374dda4c..a1ffdb0349c3a 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate enabling bundle and not configuring it + 6.2 --- diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index cd514bb44367d..6b91a65d14ae7 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -15,6 +15,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; +use Symfony\Bundle\SecurityBundle\SecurityBundle; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\FileLocator; @@ -88,6 +89,9 @@ public function prepend(ContainerBuilder $container) public function load(array $configs, ContainerBuilder $container) { if (!array_filter($configs)) { + trigger_deprecation('symfony/security-bundle', '6.3', 'Enabling bundle "%s" and not configuring it is deprecated.', SecurityBundle::class); + // uncomment the following line in 7.0 + // throw new InvalidArgumentException(sprintf('Enabling bundle "%s" and not configuring it is not allowed.', SecurityBundle::class)); return; } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index bcfb169ea321d..227d126db33e2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -848,6 +848,22 @@ public function testConfigureCustomFirewallListener() $this->assertContains('custom_firewall_listener_id', $firewallListeners); } + /** + * @group legacy + */ + public function testNothingDoneWithEmptyConfiguration() + { + $container = $this->getRawContainer(); + + $container->loadFromExtension('security'); + + $this->expectDeprecation('Since symfony/security-bundle 6.3: Enabling bundle "Symfony\Bundle\SecurityBundle\SecurityBundle" and not configuring it is deprecated.'); + + $container->compile(); + + $this->assertFalse($container->has('security.authorization_checker')); + } + protected function getRawContainer() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml index bdd94fd0afaa7..290804e61cbe6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml @@ -1,2 +1,10 @@ imports: - { resource: ./../config/framework.yml } + +security: + providers: + dummy: + memory: ~ + firewalls: + dummy: + security: false From d5f11e0bde21ae579e2d62488006aa13ff9e1d23 Mon Sep 17 00:00:00 2001 From: Jordane Vaspard Date: Sat, 26 Nov 2022 15:28:56 +0100 Subject: [PATCH 011/475] [Form] Call getChoicesForValues() once, to prevent several SQL queries --- .../Component/Form/Extension/Core/Type/ChoiceType.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 2dde39e8af9ef..65692faeb48e8 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -144,11 +144,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } } else { - foreach ($data as $value) { - if ($choiceList->getChoicesForValues([$value])) { - $knownValues[] = $value; - unset($unknownValues[$value]); - } + foreach ($choiceList->getChoicesForValues($data) as $index => $choice) { + $value = $data[$index]; + $knownValues[] = $value; + unset($unknownValues[$value]); } } From 717f668df20a3764d2341af7f33ccb648a558704 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sat, 26 Nov 2022 18:25:49 +0100 Subject: [PATCH 012/475] [Validator] Update ValidatorBuilder comment for enableAnnotationMapping --- src/Symfony/Component/Validator/ValidatorBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Validator/ValidatorBuilder.php b/src/Symfony/Component/Validator/ValidatorBuilder.php index f6dc59976e77b..fc2a5e30cebbb 100644 --- a/src/Symfony/Component/Validator/ValidatorBuilder.php +++ b/src/Symfony/Component/Validator/ValidatorBuilder.php @@ -187,7 +187,7 @@ public function addMethodMappings(array $methodNames): static } /** - * Enables annotation based constraint mapping. + * Enables annotation and attribute based constraint mapping. * * @return $this */ @@ -203,7 +203,7 @@ public function enableAnnotationMapping(): static } /** - * Disables annotation based constraint mapping. + * Disables annotation and attribute based constraint mapping. * * @return $this */ From 69f46f231b16be1bce1e2ecfa7f9a1fb192cda22 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 28 Nov 2022 14:18:19 +0100 Subject: [PATCH 013/475] [ci] Fix scorecards --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 7ee945d432674..349e215771725 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -6,7 +6,7 @@ on: schedule: - cron: '34 4 * * 6' push: - branches: [ "6.2" ] + branches: [ "6.3" ] # Declare default permissions as read only. permissions: read-all From 8e2af95ac652aa5350b288adf47b6de05384f20f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 28 Nov 2022 14:30:22 +0100 Subject: [PATCH 014/475] [Clock] Add ClockAwareTrait to help write time-sensitive classes --- src/Symfony/Component/Clock/CHANGELOG.md | 5 +++ .../Component/Clock/ClockAwareTrait.php | 36 +++++++++++++++++ .../Clock/Tests/ClockAwareTraitTest.php | 40 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/Symfony/Component/Clock/ClockAwareTrait.php create mode 100644 src/Symfony/Component/Clock/Tests/ClockAwareTraitTest.php diff --git a/src/Symfony/Component/Clock/CHANGELOG.md b/src/Symfony/Component/Clock/CHANGELOG.md index 5ffa40eff75d9..9bb1c2d4148e1 100644 --- a/src/Symfony/Component/Clock/CHANGELOG.md +++ b/src/Symfony/Component/Clock/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add `ClockAwareTrait` to help write time-sensitive classes + 6.2 --- diff --git a/src/Symfony/Component/Clock/ClockAwareTrait.php b/src/Symfony/Component/Clock/ClockAwareTrait.php new file mode 100644 index 0000000000000..22f5d4698a0aa --- /dev/null +++ b/src/Symfony/Component/Clock/ClockAwareTrait.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +use Psr\Clock\ClockInterface; +use Symfony\Contracts\Service\Attribute\Required; + +/** + * A trait to help write time-sensitive classes. + * + * @author Nicolas Grekas + */ +trait ClockAwareTrait +{ + private readonly ClockInterface $clock; + + #[Required] + public function setClock(ClockInterface $clock): void + { + $this->clock = $clock; + } + + protected function now(): \DateTimeImmutable + { + return ($this->clock ??= new NativeClock())->now(); + } +} diff --git a/src/Symfony/Component/Clock/Tests/ClockAwareTraitTest.php b/src/Symfony/Component/Clock/Tests/ClockAwareTraitTest.php new file mode 100644 index 0000000000000..c472541c64934 --- /dev/null +++ b/src/Symfony/Component/Clock/Tests/ClockAwareTraitTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Clock\ClockAwareTrait; +use Symfony\Component\Clock\MockClock; + +class ClockAwareTraitTest extends TestCase +{ + public function testTrait() + { + $sut = new class() { + use ClockAwareTrait { + now as public; + } + }; + + $this->assertInstanceOf(\DateTimeImmutable::class, $sut->now()); + + $clock = new MockClock(); + $sut = new $sut(); + $sut->setClock($clock); + + $ts = $sut->now()->getTimestamp(); + $this->assertEquals($clock->now(), $sut->now()); + $clock->sleep(1); + $this->assertEquals($clock->now(), $sut->now()); + $this->assertSame(1.0, round($sut->now()->getTimestamp() - $ts, 1)); + } +} From 269f4f168f8baac132f6926b2e2e28fd4b77f5c2 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 29 Nov 2022 08:47:20 +0100 Subject: [PATCH 015/475] [HttpKernel] Set a default file link format when none is provided to FileLinkFormatter --- src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php | 2 +- .../Component/HttpKernel/Tests/Debug/FileLinkFormatterTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php b/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php index 094465274181b..26b82715f9053 100644 --- a/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php +++ b/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php @@ -35,7 +35,7 @@ class FileLinkFormatter */ public function __construct(string|array $fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, string|\Closure $urlFormat = null) { - $fileLinkFormat ??= $_SERVER['SYMFONY_IDE'] ?? ''; + $fileLinkFormat ??= $_SERVER['SYMFONY_IDE'] ?? 'file://%f#L%l'; if (!\is_array($fileLinkFormat) && $fileLinkFormat = (ErrorRendererInterface::IDE_LINK_FORMATS[$fileLinkFormat] ?? $fileLinkFormat) ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: false) { $i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); $fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE); diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/FileLinkFormatterTest.php b/src/Symfony/Component/HttpKernel/Tests/Debug/FileLinkFormatterTest.php index d24958c6c5de5..58ae1d9168c97 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Debug/FileLinkFormatterTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Debug/FileLinkFormatterTest.php @@ -29,7 +29,7 @@ public function testAfterUnserialize() { $sut = unserialize(serialize(new FileLinkFormatter())); - $this->assertFalse($sut->format('/kernel/root/src/my/very/best/file.php', 3)); + $this->assertSame('file:///kernel/root/src/my/very/best/file.php#L3', $sut->format('/kernel/root/src/my/very/best/file.php', 3)); } public function testWhenFileLinkFormatAndNoRequest() From b8254be5814268693aab71b57352175a836918d8 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Tue, 29 Nov 2022 20:14:14 +0100 Subject: [PATCH 016/475] [Intl] Add a special locale to strip emojis easily with EmojiTransliterator --- src/Symfony/Component/Intl/CHANGELOG.md | 5 + .../data/transliterator/emoji/emoji-strip.php | 4737 +++++++++++++++++ .../Component/Intl/Resources/emoji/build.php | 15 +- .../EmojiTransliteratorTest.php | 17 + 4 files changed, 4773 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-strip.php diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index 10fd1af30c0b4..3a84dd3d42d56 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add the special `strip` locale to `EmojiTransliterator` to strip all emojis from a string + 6.2 --- diff --git a/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-strip.php b/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-strip.php new file mode 100644 index 0000000000000..a6d1d78ae9adf --- /dev/null +++ b/src/Symfony/Component/Intl/Resources/data/transliterator/emoji/emoji-strip.php @@ -0,0 +1,4737 @@ + '', + '🧑🏻‍❤️‍💋‍🧑🏽' => '', + '🧑🏻‍❤️‍💋‍🧑🏾' => '', + '🧑🏻‍❤️‍💋‍🧑🏿' => '', + '🧑🏼‍❤️‍💋‍🧑🏻' => '', + '🧑🏼‍❤️‍💋‍🧑🏽' => '', + '🧑🏼‍❤️‍💋‍🧑🏾' => '', + '🧑🏼‍❤️‍💋‍🧑🏿' => '', + '🧑🏽‍❤️‍💋‍🧑🏻' => '', + '🧑🏽‍❤️‍💋‍🧑🏼' => '', + '🧑🏽‍❤️‍💋‍🧑🏾' => '', + '🧑🏽‍❤️‍💋‍🧑🏿' => '', + '🧑🏾‍❤️‍💋‍🧑🏻' => '', + '🧑🏾‍❤️‍💋‍🧑🏼' => '', + '🧑🏾‍❤️‍💋‍🧑🏽' => '', + '🧑🏾‍❤️‍💋‍🧑🏿' => '', + '🧑🏿‍❤️‍💋‍🧑🏻' => '', + '🧑🏿‍❤️‍💋‍🧑🏼' => '', + '🧑🏿‍❤️‍💋‍🧑🏽' => '', + '🧑🏿‍❤️‍💋‍🧑🏾' => '', + '👩🏻‍❤️‍💋‍👨🏻' => '', + '👩🏻‍❤️‍💋‍👨🏼' => '', + '👩🏻‍❤️‍💋‍👨🏽' => '', + '👩🏻‍❤️‍💋‍👨🏾' => '', + '👩🏻‍❤️‍💋‍👨🏿' => '', + '👩🏼‍❤️‍💋‍👨🏻' => '', + '👩🏼‍❤️‍💋‍👨🏼' => '', + '👩🏼‍❤️‍💋‍👨🏽' => '', + '👩🏼‍❤️‍💋‍👨🏾' => '', + '👩🏼‍❤️‍💋‍👨🏿' => '', + '👩🏽‍❤️‍💋‍👨🏻' => '', + '👩🏽‍❤️‍💋‍👨🏼' => '', + '👩🏽‍❤️‍💋‍👨🏽' => '', + '👩🏽‍❤️‍💋‍👨🏾' => '', + '👩🏽‍❤️‍💋‍👨🏿' => '', + '👩🏾‍❤️‍💋‍👨🏻' => '', + '👩🏾‍❤️‍💋‍👨🏼' => '', + '👩🏾‍❤️‍💋‍👨🏽' => '', + '👩🏾‍❤️‍💋‍👨🏾' => '', + '👩🏾‍❤️‍💋‍👨🏿' => '', + '👩🏿‍❤️‍💋‍👨🏻' => '', + '👩🏿‍❤️‍💋‍👨🏼' => '', + '👩🏿‍❤️‍💋‍👨🏽' => '', + '👩🏿‍❤️‍💋‍👨🏾' => '', + '👩🏿‍❤️‍💋‍👨🏿' => '', + '👨🏻‍❤️‍💋‍👨🏻' => '', + '👨🏻‍❤️‍💋‍👨🏼' => '', + '👨🏻‍❤️‍💋‍👨🏽' => '', + '👨🏻‍❤️‍💋‍👨🏾' => '', + '👨🏻‍❤️‍💋‍👨🏿' => '', + '👨🏼‍❤️‍💋‍👨🏻' => '', + '👨🏼‍❤️‍💋‍👨🏼' => '', + '👨🏼‍❤️‍💋‍👨🏽' => '', + '👨🏼‍❤️‍💋‍👨🏾' => '', + '👨🏼‍❤️‍💋‍👨🏿' => '', + '👨🏽‍❤️‍💋‍👨🏻' => '', + '👨🏽‍❤️‍💋‍👨🏼' => '', + '👨🏽‍❤️‍💋‍👨🏽' => '', + '👨🏽‍❤️‍💋‍👨🏾' => '', + '👨🏽‍❤️‍💋‍👨🏿' => '', + '👨🏾‍❤️‍💋‍👨🏻' => '', + '👨🏾‍❤️‍💋‍👨🏼' => '', + '👨🏾‍❤️‍💋‍👨🏽' => '', + '👨🏾‍❤️‍💋‍👨🏾' => '', + '👨🏾‍❤️‍💋‍👨🏿' => '', + '👨🏿‍❤️‍💋‍👨🏻' => '', + '👨🏿‍❤️‍💋‍👨🏼' => '', + '👨🏿‍❤️‍💋‍👨🏽' => '', + '👨🏿‍❤️‍💋‍👨🏾' => '', + '👨🏿‍❤️‍💋‍👨🏿' => '', + '👩🏻‍❤️‍💋‍👩🏻' => '', + '👩🏻‍❤️‍💋‍👩🏼' => '', + '👩🏻‍❤️‍💋‍👩🏽' => '', + '👩🏻‍❤️‍💋‍👩🏾' => '', + '👩🏻‍❤️‍💋‍👩🏿' => '', + '👩🏼‍❤️‍💋‍👩🏻' => '', + '👩🏼‍❤️‍💋‍👩🏼' => '', + '👩🏼‍❤️‍💋‍👩🏽' => '', + '👩🏼‍❤️‍💋‍👩🏾' => '', + '👩🏼‍❤️‍💋‍👩🏿' => '', + '👩🏽‍❤️‍💋‍👩🏻' => '', + '👩🏽‍❤️‍💋‍👩🏼' => '', + '👩🏽‍❤️‍💋‍👩🏽' => '', + '👩🏽‍❤️‍💋‍👩🏾' => '', + '👩🏽‍❤️‍💋‍👩🏿' => '', + '👩🏾‍❤️‍💋‍👩🏻' => '', + '👩🏾‍❤️‍💋‍👩🏼' => '', + '👩🏾‍❤️‍💋‍👩🏽' => '', + '👩🏾‍❤️‍💋‍👩🏾' => '', + '👩🏾‍❤️‍💋‍👩🏿' => '', + '👩🏿‍❤️‍💋‍👩🏻' => '', + '👩🏿‍❤️‍💋‍👩🏼' => '', + '👩🏿‍❤️‍💋‍👩🏽' => '', + '👩🏿‍❤️‍💋‍👩🏾' => '', + '👩🏿‍❤️‍💋‍👩🏿' => '', + '🧑🏻‍❤‍💋‍🧑🏼' => '', + '🧑🏻‍❤‍💋‍🧑🏽' => '', + '🧑🏻‍❤‍💋‍🧑🏾' => '', + '🧑🏻‍❤‍💋‍🧑🏿' => '', + '🧑🏼‍❤‍💋‍🧑🏻' => '', + '🧑🏼‍❤‍💋‍🧑🏽' => '', + '🧑🏼‍❤‍💋‍🧑🏾' => '', + '🧑🏼‍❤‍💋‍🧑🏿' => '', + '🧑🏽‍❤‍💋‍🧑🏻' => '', + '🧑🏽‍❤‍💋‍🧑🏼' => '', + '🧑🏽‍❤‍💋‍🧑🏾' => '', + '🧑🏽‍❤‍💋‍🧑🏿' => '', + '🧑🏾‍❤‍💋‍🧑🏻' => '', + '🧑🏾‍❤‍💋‍🧑🏼' => '', + '🧑🏾‍❤‍💋‍🧑🏽' => '', + '🧑🏾‍❤‍💋‍🧑🏿' => '', + '🧑🏿‍❤‍💋‍🧑🏻' => '', + '🧑🏿‍❤‍💋‍🧑🏼' => '', + '🧑🏿‍❤‍💋‍🧑🏽' => '', + '🧑🏿‍❤‍💋‍🧑🏾' => '', + '👩🏻‍❤‍💋‍👨🏻' => '', + '👩🏻‍❤‍💋‍👨🏼' => '', + '👩🏻‍❤‍💋‍👨🏽' => '', + '👩🏻‍❤‍💋‍👨🏾' => '', + '👩🏻‍❤‍💋‍👨🏿' => '', + '👩🏼‍❤‍💋‍👨🏻' => '', + '👩🏼‍❤‍💋‍👨🏼' => '', + '👩🏼‍❤‍💋‍👨🏽' => '', + '👩🏼‍❤‍💋‍👨🏾' => '', + '👩🏼‍❤‍💋‍👨🏿' => '', + '👩🏽‍❤‍💋‍👨🏻' => '', + '👩🏽‍❤‍💋‍👨🏼' => '', + '👩🏽‍❤‍💋‍👨🏽' => '', + '👩🏽‍❤‍💋‍👨🏾' => '', + '👩🏽‍❤‍💋‍👨🏿' => '', + '👩🏾‍❤‍💋‍👨🏻' => '', + '👩🏾‍❤‍💋‍👨🏼' => '', + '👩🏾‍❤‍💋‍👨🏽' => '', + '👩🏾‍❤‍💋‍👨🏾' => '', + '👩🏾‍❤‍💋‍👨🏿' => '', + '👩🏿‍❤‍💋‍👨🏻' => '', + '👩🏿‍❤‍💋‍👨🏼' => '', + '👩🏿‍❤‍💋‍👨🏽' => '', + '👩🏿‍❤‍💋‍👨🏾' => '', + '👩🏿‍❤‍💋‍👨🏿' => '', + '👨🏻‍❤‍💋‍👨🏻' => '', + '👨🏻‍❤‍💋‍👨🏼' => '', + '👨🏻‍❤‍💋‍👨🏽' => '', + '👨🏻‍❤‍💋‍👨🏾' => '', + '👨🏻‍❤‍💋‍👨🏿' => '', + '👨🏼‍❤‍💋‍👨🏻' => '', + '👨🏼‍❤‍💋‍👨🏼' => '', + '👨🏼‍❤‍💋‍👨🏽' => '', + '👨🏼‍❤‍💋‍👨🏾' => '', + '👨🏼‍❤‍💋‍👨🏿' => '', + '👨🏽‍❤‍💋‍👨🏻' => '', + '👨🏽‍❤‍💋‍👨🏼' => '', + '👨🏽‍❤‍💋‍👨🏽' => '', + '👨🏽‍❤‍💋‍👨🏾' => '', + '👨🏽‍❤‍💋‍👨🏿' => '', + '👨🏾‍❤‍💋‍👨🏻' => '', + '👨🏾‍❤‍💋‍👨🏼' => '', + '👨🏾‍❤‍💋‍👨🏽' => '', + '👨🏾‍❤‍💋‍👨🏾' => '', + '👨🏾‍❤‍💋‍👨🏿' => '', + '👨🏿‍❤‍💋‍👨🏻' => '', + '👨🏿‍❤‍💋‍👨🏼' => '', + '👨🏿‍❤‍💋‍👨🏽' => '', + '👨🏿‍❤‍💋‍👨🏾' => '', + '👨🏿‍❤‍💋‍👨🏿' => '', + '👩🏻‍❤‍💋‍👩🏻' => '', + '👩🏻‍❤‍💋‍👩🏼' => '', + '👩🏻‍❤‍💋‍👩🏽' => '', + '👩🏻‍❤‍💋‍👩🏾' => '', + '👩🏻‍❤‍💋‍👩🏿' => '', + '👩🏼‍❤‍💋‍👩🏻' => '', + '👩🏼‍❤‍💋‍👩🏼' => '', + '👩🏼‍❤‍💋‍👩🏽' => '', + '👩🏼‍❤‍💋‍👩🏾' => '', + '👩🏼‍❤‍💋‍👩🏿' => '', + '👩🏽‍❤‍💋‍👩🏻' => '', + '👩🏽‍❤‍💋‍👩🏼' => '', + '👩🏽‍❤‍💋‍👩🏽' => '', + '👩🏽‍❤‍💋‍👩🏾' => '', + '👩🏽‍❤‍💋‍👩🏿' => '', + '👩🏾‍❤‍💋‍👩🏻' => '', + '👩🏾‍❤‍💋‍👩🏼' => '', + '👩🏾‍❤‍💋‍👩🏽' => '', + '👩🏾‍❤‍💋‍👩🏾' => '', + '👩🏾‍❤‍💋‍👩🏿' => '', + '👩🏿‍❤‍💋‍👩🏻' => '', + '👩🏿‍❤‍💋‍👩🏼' => '', + '👩🏿‍❤‍💋‍👩🏽' => '', + '👩🏿‍❤‍💋‍👩🏾' => '', + '👩🏿‍❤‍💋‍👩🏿' => '', + '👩‍❤️‍💋‍👨' => '', + '👨‍❤️‍💋‍👨' => '', + '👩‍❤️‍💋‍👩' => '', + '🧑🏻‍❤️‍🧑🏼' => '', + '🧑🏻‍❤️‍🧑🏽' => '', + '🧑🏻‍❤️‍🧑🏾' => '', + '🧑🏻‍❤️‍🧑🏿' => '', + '🧑🏼‍❤️‍🧑🏻' => '', + '🧑🏼‍❤️‍🧑🏽' => '', + '🧑🏼‍❤️‍🧑🏾' => '', + '🧑🏼‍❤️‍🧑🏿' => '', + '🧑🏽‍❤️‍🧑🏻' => '', + '🧑🏽‍❤️‍🧑🏼' => '', + '🧑🏽‍❤️‍🧑🏾' => '', + '🧑🏽‍❤️‍🧑🏿' => '', + '🧑🏾‍❤️‍🧑🏻' => '', + '🧑🏾‍❤️‍🧑🏼' => '', + '🧑🏾‍❤️‍🧑🏽' => '', + '🧑🏾‍❤️‍🧑🏿' => '', + '🧑🏿‍❤️‍🧑🏻' => '', + '🧑🏿‍❤️‍🧑🏼' => '', + '🧑🏿‍❤️‍🧑🏽' => '', + '🧑🏿‍❤️‍🧑🏾' => '', + '👩🏻‍❤️‍👨🏻' => '', + '👩🏻‍❤️‍👨🏼' => '', + '👩🏻‍❤️‍👨🏽' => '', + '👩🏻‍❤️‍👨🏾' => '', + '👩🏻‍❤️‍👨🏿' => '', + '👩🏼‍❤️‍👨🏻' => '', + '👩🏼‍❤️‍👨🏼' => '', + '👩🏼‍❤️‍👨🏽' => '', + '👩🏼‍❤️‍👨🏾' => '', + '👩🏼‍❤️‍👨🏿' => '', + '👩🏽‍❤️‍👨🏻' => '', + '👩🏽‍❤️‍👨🏼' => '', + '👩🏽‍❤️‍👨🏽' => '', + '👩🏽‍❤️‍👨🏾' => '', + '👩🏽‍❤️‍👨🏿' => '', + '👩🏾‍❤️‍👨🏻' => '', + '👩🏾‍❤️‍👨🏼' => '', + '👩🏾‍❤️‍👨🏽' => '', + '👩🏾‍❤️‍👨🏾' => '', + '👩🏾‍❤️‍👨🏿' => '', + '👩🏿‍❤️‍👨🏻' => '', + '👩🏿‍❤️‍👨🏼' => '', + '👩🏿‍❤️‍👨🏽' => '', + '👩🏿‍❤️‍👨🏾' => '', + '👩🏿‍❤️‍👨🏿' => '', + '👨🏻‍❤️‍👨🏻' => '', + '👨🏻‍❤️‍👨🏼' => '', + '👨🏻‍❤️‍👨🏽' => '', + '👨🏻‍❤️‍👨🏾' => '', + '👨🏻‍❤️‍👨🏿' => '', + '👨🏼‍❤️‍👨🏻' => '', + '👨🏼‍❤️‍👨🏼' => '', + '👨🏼‍❤️‍👨🏽' => '', + '👨🏼‍❤️‍👨🏾' => '', + '👨🏼‍❤️‍👨🏿' => '', + '👨🏽‍❤️‍👨🏻' => '', + '👨🏽‍❤️‍👨🏼' => '', + '👨🏽‍❤️‍👨🏽' => '', + '👨🏽‍❤️‍👨🏾' => '', + '👨🏽‍❤️‍👨🏿' => '', + '👨🏾‍❤️‍👨🏻' => '', + '👨🏾‍❤️‍👨🏼' => '', + '👨🏾‍❤️‍👨🏽' => '', + '👨🏾‍❤️‍👨🏾' => '', + '👨🏾‍❤️‍👨🏿' => '', + '👨🏿‍❤️‍👨🏻' => '', + '👨🏿‍❤️‍👨🏼' => '', + '👨🏿‍❤️‍👨🏽' => '', + '👨🏿‍❤️‍👨🏾' => '', + '👨🏿‍❤️‍👨🏿' => '', + '👩🏻‍❤️‍👩🏻' => '', + '👩🏻‍❤️‍👩🏼' => '', + '👩🏻‍❤️‍👩🏽' => '', + '👩🏻‍❤️‍👩🏾' => '', + '👩🏻‍❤️‍👩🏿' => '', + '👩🏼‍❤️‍👩🏻' => '', + '👩🏼‍❤️‍👩🏼' => '', + '👩🏼‍❤️‍👩🏽' => '', + '👩🏼‍❤️‍👩🏾' => '', + '👩🏼‍❤️‍👩🏿' => '', + '👩🏽‍❤️‍👩🏻' => '', + '👩🏽‍❤️‍👩🏼' => '', + '👩🏽‍❤️‍👩🏽' => '', + '👩🏽‍❤️‍👩🏾' => '', + '👩🏽‍❤️‍👩🏿' => '', + '👩🏾‍❤️‍👩🏻' => '', + '👩🏾‍❤️‍👩🏼' => '', + '👩🏾‍❤️‍👩🏽' => '', + '👩🏾‍❤️‍👩🏾' => '', + '👩🏾‍❤️‍👩🏿' => '', + '👩🏿‍❤️‍👩🏻' => '', + '👩🏿‍❤️‍👩🏼' => '', + '👩🏿‍❤️‍👩🏽' => '', + '👩🏿‍❤️‍👩🏾' => '', + '👩🏿‍❤️‍👩🏿' => '', + '🧑🏻‍🤝‍🧑🏻' => '', + '🧑🏻‍🤝‍🧑🏼' => '', + '🧑🏻‍🤝‍🧑🏽' => '', + '🧑🏻‍🤝‍🧑🏾' => '', + '🧑🏻‍🤝‍🧑🏿' => '', + '🧑🏼‍🤝‍🧑🏻' => '', + '🧑🏼‍🤝‍🧑🏼' => '', + '🧑🏼‍🤝‍🧑🏽' => '', + '🧑🏼‍🤝‍🧑🏾' => '', + '🧑🏼‍🤝‍🧑🏿' => '', + '🧑🏽‍🤝‍🧑🏻' => '', + '🧑🏽‍🤝‍🧑🏼' => '', + '🧑🏽‍🤝‍🧑🏽' => '', + '🧑🏽‍🤝‍🧑🏾' => '', + '🧑🏽‍🤝‍🧑🏿' => '', + '🧑🏾‍🤝‍🧑🏻' => '', + '🧑🏾‍🤝‍🧑🏼' => '', + '🧑🏾‍🤝‍🧑🏽' => '', + '🧑🏾‍🤝‍🧑🏾' => '', + '🧑🏾‍🤝‍🧑🏿' => '', + '🧑🏿‍🤝‍🧑🏻' => '', + '🧑🏿‍🤝‍🧑🏼' => '', + '🧑🏿‍🤝‍🧑🏽' => '', + '🧑🏿‍🤝‍🧑🏾' => '', + '🧑🏿‍🤝‍🧑🏿' => '', + '👩🏻‍🤝‍👩🏼' => '', + '👩🏻‍🤝‍👩🏽' => '', + '👩🏻‍🤝‍👩🏾' => '', + '👩🏻‍🤝‍👩🏿' => '', + '👩🏼‍🤝‍👩🏻' => '', + '👩🏼‍🤝‍👩🏽' => '', + '👩🏼‍🤝‍👩🏾' => '', + '👩🏼‍🤝‍👩🏿' => '', + '👩🏽‍🤝‍👩🏻' => '', + '👩🏽‍🤝‍👩🏼' => '', + '👩🏽‍🤝‍👩🏾' => '', + '👩🏽‍🤝‍👩🏿' => '', + '👩🏾‍🤝‍👩🏻' => '', + '👩🏾‍🤝‍👩🏼' => '', + '👩🏾‍🤝‍👩🏽' => '', + '👩🏾‍🤝‍👩🏿' => '', + '👩🏿‍🤝‍👩🏻' => '', + '👩🏿‍🤝‍👩🏼' => '', + '👩🏿‍🤝‍👩🏽' => '', + '👩🏿‍🤝‍👩🏾' => '', + '👩🏻‍🤝‍👨🏼' => '', + '👩🏻‍🤝‍👨🏽' => '', + '👩🏻‍🤝‍👨🏾' => '', + '👩🏻‍🤝‍👨🏿' => '', + '👩🏼‍🤝‍👨🏻' => '', + '👩🏼‍🤝‍👨🏽' => '', + '👩🏼‍🤝‍👨🏾' => '', + '👩🏼‍🤝‍👨🏿' => '', + '👩🏽‍🤝‍👨🏻' => '', + '👩🏽‍🤝‍👨🏼' => '', + '👩🏽‍🤝‍👨🏾' => '', + '👩🏽‍🤝‍👨🏿' => '', + '👩🏾‍🤝‍👨🏻' => '', + '👩🏾‍🤝‍👨🏼' => '', + '👩🏾‍🤝‍👨🏽' => '', + '👩🏾‍🤝‍👨🏿' => '', + '👩🏿‍🤝‍👨🏻' => '', + '👩🏿‍🤝‍👨🏼' => '', + '👩🏿‍🤝‍👨🏽' => '', + '👩🏿‍🤝‍👨🏾' => '', + '👨🏻‍🤝‍👨🏼' => '', + '👨🏻‍🤝‍👨🏽' => '', + '👨🏻‍🤝‍👨🏾' => '', + '👨🏻‍🤝‍👨🏿' => '', + '👨🏼‍🤝‍👨🏻' => '', + '👨🏼‍🤝‍👨🏽' => '', + '👨🏼‍🤝‍👨🏾' => '', + '👨🏼‍🤝‍👨🏿' => '', + '👨🏽‍🤝‍👨🏻' => '', + '👨🏽‍🤝‍👨🏼' => '', + '👨🏽‍🤝‍👨🏾' => '', + '👨🏽‍🤝‍👨🏿' => '', + '👨🏾‍🤝‍👨🏻' => '', + '👨🏾‍🤝‍👨🏼' => '', + '👨🏾‍🤝‍👨🏽' => '', + '👨🏾‍🤝‍👨🏿' => '', + '👨🏿‍🤝‍👨🏻' => '', + '👨🏿‍🤝‍👨🏼' => '', + '👨🏿‍🤝‍👨🏽' => '', + '👨🏿‍🤝‍👨🏾' => '', + '👩‍❤‍💋‍👨' => '', + '👨‍❤‍💋‍👨' => '', + '👩‍❤‍💋‍👩' => '', + '🧑🏻‍❤‍🧑🏼' => '', + '🧑🏻‍❤‍🧑🏽' => '', + '🧑🏻‍❤‍🧑🏾' => '', + '🧑🏻‍❤‍🧑🏿' => '', + '🧑🏼‍❤‍🧑🏻' => '', + '🧑🏼‍❤‍🧑🏽' => '', + '🧑🏼‍❤‍🧑🏾' => '', + '🧑🏼‍❤‍🧑🏿' => '', + '🧑🏽‍❤‍🧑🏻' => '', + '🧑🏽‍❤‍🧑🏼' => '', + '🧑🏽‍❤‍🧑🏾' => '', + '🧑🏽‍❤‍🧑🏿' => '', + '🧑🏾‍❤‍🧑🏻' => '', + '🧑🏾‍❤‍🧑🏼' => '', + '🧑🏾‍❤‍🧑🏽' => '', + '🧑🏾‍❤‍🧑🏿' => '', + '🧑🏿‍❤‍🧑🏻' => '', + '🧑🏿‍❤‍🧑🏼' => '', + '🧑🏿‍❤‍🧑🏽' => '', + '🧑🏿‍❤‍🧑🏾' => '', + '👩🏻‍❤‍👨🏻' => '', + '👩🏻‍❤‍👨🏼' => '', + '👩🏻‍❤‍👨🏽' => '', + '👩🏻‍❤‍👨🏾' => '', + '👩🏻‍❤‍👨🏿' => '', + '👩🏼‍❤‍👨🏻' => '', + '👩🏼‍❤‍👨🏼' => '', + '👩🏼‍❤‍👨🏽' => '', + '👩🏼‍❤‍👨🏾' => '', + '👩🏼‍❤‍👨🏿' => '', + '👩🏽‍❤‍👨🏻' => '', + '👩🏽‍❤‍👨🏼' => '', + '👩🏽‍❤‍👨🏽' => '', + '👩🏽‍❤‍👨🏾' => '', + '👩🏽‍❤‍👨🏿' => '', + '👩🏾‍❤‍👨🏻' => '', + '👩🏾‍❤‍👨🏼' => '', + '👩🏾‍❤‍👨🏽' => '', + '👩🏾‍❤‍👨🏾' => '', + '👩🏾‍❤‍👨🏿' => '', + '👩🏿‍❤‍👨🏻' => '', + '👩🏿‍❤‍👨🏼' => '', + '👩🏿‍❤‍👨🏽' => '', + '👩🏿‍❤‍👨🏾' => '', + '👩🏿‍❤‍👨🏿' => '', + '👨🏻‍❤‍👨🏻' => '', + '👨🏻‍❤‍👨🏼' => '', + '👨🏻‍❤‍👨🏽' => '', + '👨🏻‍❤‍👨🏾' => '', + '👨🏻‍❤‍👨🏿' => '', + '👨🏼‍❤‍👨🏻' => '', + '👨🏼‍❤‍👨🏼' => '', + '👨🏼‍❤‍👨🏽' => '', + '👨🏼‍❤‍👨🏾' => '', + '👨🏼‍❤‍👨🏿' => '', + '👨🏽‍❤‍👨🏻' => '', + '👨🏽‍❤‍👨🏼' => '', + '👨🏽‍❤‍👨🏽' => '', + '👨🏽‍❤‍👨🏾' => '', + '👨🏽‍❤‍👨🏿' => '', + '👨🏾‍❤‍👨🏻' => '', + '👨🏾‍❤‍👨🏼' => '', + '👨🏾‍❤‍👨🏽' => '', + '👨🏾‍❤‍👨🏾' => '', + '👨🏾‍❤‍👨🏿' => '', + '👨🏿‍❤‍👨🏻' => '', + '👨🏿‍❤‍👨🏼' => '', + '👨🏿‍❤‍👨🏽' => '', + '👨🏿‍❤‍👨🏾' => '', + '👨🏿‍❤‍👨🏿' => '', + '👩🏻‍❤‍👩🏻' => '', + '👩🏻‍❤‍👩🏼' => '', + '👩🏻‍❤‍👩🏽' => '', + '👩🏻‍❤‍👩🏾' => '', + '👩🏻‍❤‍👩🏿' => '', + '👩🏼‍❤‍👩🏻' => '', + '👩🏼‍❤‍👩🏼' => '', + '👩🏼‍❤‍👩🏽' => '', + '👩🏼‍❤‍👩🏾' => '', + '👩🏼‍❤‍👩🏿' => '', + '👩🏽‍❤‍👩🏻' => '', + '👩🏽‍❤‍👩🏼' => '', + '👩🏽‍❤‍👩🏽' => '', + '👩🏽‍❤‍👩🏾' => '', + '👩🏽‍❤‍👩🏿' => '', + '👩🏾‍❤‍👩🏻' => '', + '👩🏾‍❤‍👩🏼' => '', + '👩🏾‍❤‍👩🏽' => '', + '👩🏾‍❤‍👩🏾' => '', + '👩🏾‍❤‍👩🏿' => '', + '👩🏿‍❤‍👩🏻' => '', + '👩🏿‍❤‍👩🏼' => '', + '👩🏿‍❤‍👩🏽' => '', + '👩🏿‍❤‍👩🏾' => '', + '👩🏿‍❤‍👩🏿' => '', + '👨‍👩‍👧‍👦' => '', + '👨‍👩‍👦‍👦' => '', + '👨‍👩‍👧‍👧' => '', + '👨‍👨‍👧‍👦' => '', + '👨‍👨‍👦‍👦' => '', + '👨‍👨‍👧‍👧' => '', + '👩‍👩‍👧‍👦' => '', + '👩‍👩‍👦‍👦' => '', + '👩‍👩‍👧‍👧' => '', + '🏴󠁧󠁢󠁥󠁮󠁧󠁿' => '', + '🏴󠁧󠁢󠁳󠁣󠁴󠁿' => '', + '🏴󠁧󠁢󠁷󠁬󠁳󠁿' => '', + '👩‍❤️‍👨' => '', + '👨‍❤️‍👨' => '', + '👩‍❤️‍👩' => '', + '👁️‍🗨️' => '', + '🫱🏻‍🫲🏼' => '', + '🫱🏻‍🫲🏽' => '', + '🫱🏻‍🫲🏾' => '', + '🫱🏻‍🫲🏿' => '', + '🫱🏼‍🫲🏻' => '', + '🫱🏼‍🫲🏽' => '', + '🫱🏼‍🫲🏾' => '', + '🫱🏼‍🫲🏿' => '', + '🫱🏽‍🫲🏻' => '', + '🫱🏽‍🫲🏼' => '', + '🫱🏽‍🫲🏾' => '', + '🫱🏽‍🫲🏿' => '', + '🫱🏾‍🫲🏻' => '', + '🫱🏾‍🫲🏼' => '', + '🫱🏾‍🫲🏽' => '', + '🫱🏾‍🫲🏿' => '', + '🫱🏿‍🫲🏻' => '', + '🫱🏿‍🫲🏼' => '', + '🫱🏿‍🫲🏽' => '', + '🫱🏿‍🫲🏾' => '', + '🧔🏻‍♂️' => '', + '🧔🏼‍♂️' => '', + '🧔🏽‍♂️' => '', + '🧔🏾‍♂️' => '', + '🧔🏿‍♂️' => '', + '🧔🏻‍♀️' => '', + '🧔🏼‍♀️' => '', + '🧔🏽‍♀️' => '', + '🧔🏾‍♀️' => '', + '🧔🏿‍♀️' => '', + '👱🏻‍♀️' => '', + '👱🏼‍♀️' => '', + '👱🏽‍♀️' => '', + '👱🏾‍♀️' => '', + '👱🏿‍♀️' => '', + '👱🏻‍♂️' => '', + '👱🏼‍♂️' => '', + '👱🏽‍♂️' => '', + '👱🏾‍♂️' => '', + '👱🏿‍♂️' => '', + '🙍🏻‍♂️' => '', + '🙍🏼‍♂️' => '', + '🙍🏽‍♂️' => '', + '🙍🏾‍♂️' => '', + '🙍🏿‍♂️' => '', + '🙍🏻‍♀️' => '', + '🙍🏼‍♀️' => '', + '🙍🏽‍♀️' => '', + '🙍🏾‍♀️' => '', + '🙍🏿‍♀️' => '', + '🙎🏻‍♂️' => '', + '🙎🏼‍♂️' => '', + '🙎🏽‍♂️' => '', + '🙎🏾‍♂️' => '', + '🙎🏿‍♂️' => '', + '🙎🏻‍♀️' => '', + '🙎🏼‍♀️' => '', + '🙎🏽‍♀️' => '', + '🙎🏾‍♀️' => '', + '🙎🏿‍♀️' => '', + '🙅🏻‍♂️' => '', + '🙅🏼‍♂️' => '', + '🙅🏽‍♂️' => '', + '🙅🏾‍♂️' => '', + '🙅🏿‍♂️' => '', + '🙅🏻‍♀️' => '', + '🙅🏼‍♀️' => '', + '🙅🏽‍♀️' => '', + '🙅🏾‍♀️' => '', + '🙅🏿‍♀️' => '', + '🙆🏻‍♂️' => '', + '🙆🏼‍♂️' => '', + '🙆🏽‍♂️' => '', + '🙆🏾‍♂️' => '', + '🙆🏿‍♂️' => '', + '🙆🏻‍♀️' => '', + '🙆🏼‍♀️' => '', + '🙆🏽‍♀️' => '', + '🙆🏾‍♀️' => '', + '🙆🏿‍♀️' => '', + '💁🏻‍♂️' => '', + '💁🏼‍♂️' => '', + '💁🏽‍♂️' => '', + '💁🏾‍♂️' => '', + '💁🏿‍♂️' => '', + '💁🏻‍♀️' => '', + '💁🏼‍♀️' => '', + '💁🏽‍♀️' => '', + '💁🏾‍♀️' => '', + '💁🏿‍♀️' => '', + '🙋🏻‍♂️' => '', + '🙋🏼‍♂️' => '', + '🙋🏽‍♂️' => '', + '🙋🏾‍♂️' => '', + '🙋🏿‍♂️' => '', + '🙋🏻‍♀️' => '', + '🙋🏼‍♀️' => '', + '🙋🏽‍♀️' => '', + '🙋🏾‍♀️' => '', + '🙋🏿‍♀️' => '', + '🧏🏻‍♂️' => '', + '🧏🏼‍♂️' => '', + '🧏🏽‍♂️' => '', + '🧏🏾‍♂️' => '', + '🧏🏿‍♂️' => '', + '🧏🏻‍♀️' => '', + '🧏🏼‍♀️' => '', + '🧏🏽‍♀️' => '', + '🧏🏾‍♀️' => '', + '🧏🏿‍♀️' => '', + '🙇🏻‍♂️' => '', + '🙇🏼‍♂️' => '', + '🙇🏽‍♂️' => '', + '🙇🏾‍♂️' => '', + '🙇🏿‍♂️' => '', + '🙇🏻‍♀️' => '', + '🙇🏼‍♀️' => '', + '🙇🏽‍♀️' => '', + '🙇🏾‍♀️' => '', + '🙇🏿‍♀️' => '', + '🤦🏻‍♂️' => '', + '🤦🏼‍♂️' => '', + '🤦🏽‍♂️' => '', + '🤦🏾‍♂️' => '', + '🤦🏿‍♂️' => '', + '🤦🏻‍♀️' => '', + '🤦🏼‍♀️' => '', + '🤦🏽‍♀️' => '', + '🤦🏾‍♀️' => '', + '🤦🏿‍♀️' => '', + '🤷🏻‍♂️' => '', + '🤷🏼‍♂️' => '', + '🤷🏽‍♂️' => '', + '🤷🏾‍♂️' => '', + '🤷🏿‍♂️' => '', + '🤷🏻‍♀️' => '', + '🤷🏼‍♀️' => '', + '🤷🏽‍♀️' => '', + '🤷🏾‍♀️' => '', + '🤷🏿‍♀️' => '', + '🧑🏻‍⚕️' => '', + '🧑🏼‍⚕️' => '', + '🧑🏽‍⚕️' => '', + '🧑🏾‍⚕️' => '', + '🧑🏿‍⚕️' => '', + '👨🏻‍⚕️' => '', + '👨🏼‍⚕️' => '', + '👨🏽‍⚕️' => '', + '👨🏾‍⚕️' => '', + '👨🏿‍⚕️' => '', + '👩🏻‍⚕️' => '', + '👩🏼‍⚕️' => '', + '👩🏽‍⚕️' => '', + '👩🏾‍⚕️' => '', + '👩🏿‍⚕️' => '', + '🧑🏻‍⚖️' => '', + '🧑🏼‍⚖️' => '', + '🧑🏽‍⚖️' => '', + '🧑🏾‍⚖️' => '', + '🧑🏿‍⚖️' => '', + '👨🏻‍⚖️' => '', + '👨🏼‍⚖️' => '', + '👨🏽‍⚖️' => '', + '👨🏾‍⚖️' => '', + '👨🏿‍⚖️' => '', + '👩🏻‍⚖️' => '', + '👩🏼‍⚖️' => '', + '👩🏽‍⚖️' => '', + '👩🏾‍⚖️' => '', + '👩🏿‍⚖️' => '', + '🧑🏻‍✈️' => '', + '🧑🏼‍✈️' => '', + '🧑🏽‍✈️' => '', + '🧑🏾‍✈️' => '', + '🧑🏿‍✈️' => '', + '👨🏻‍✈️' => '', + '👨🏼‍✈️' => '', + '👨🏽‍✈️' => '', + '👨🏾‍✈️' => '', + '👨🏿‍✈️' => '', + '👩🏻‍✈️' => '', + '👩🏼‍✈️' => '', + '👩🏽‍✈️' => '', + '👩🏾‍✈️' => '', + '👩🏿‍✈️' => '', + '👮🏻‍♂️' => '', + '👮🏼‍♂️' => '', + '👮🏽‍♂️' => '', + '👮🏾‍♂️' => '', + '👮🏿‍♂️' => '', + '👮🏻‍♀️' => '', + '👮🏼‍♀️' => '', + '👮🏽‍♀️' => '', + '👮🏾‍♀️' => '', + '👮🏿‍♀️' => '', + '🕵️‍♂️' => '', + '🕵🏻‍♂️' => '', + '🕵🏼‍♂️' => '', + '🕵🏽‍♂️' => '', + '🕵🏾‍♂️' => '', + '🕵🏿‍♂️' => '', + '🕵️‍♀️' => '', + '🕵🏻‍♀️' => '', + '🕵🏼‍♀️' => '', + '🕵🏽‍♀️' => '', + '🕵🏾‍♀️' => '', + '🕵🏿‍♀️' => '', + '💂🏻‍♂️' => '', + '💂🏼‍♂️' => '', + '💂🏽‍♂️' => '', + '💂🏾‍♂️' => '', + '💂🏿‍♂️' => '', + '💂🏻‍♀️' => '', + '💂🏼‍♀️' => '', + '💂🏽‍♀️' => '', + '💂🏾‍♀️' => '', + '💂🏿‍♀️' => '', + '👷🏻‍♂️' => '', + '👷🏼‍♂️' => '', + '👷🏽‍♂️' => '', + '👷🏾‍♂️' => '', + '👷🏿‍♂️' => '', + '👷🏻‍♀️' => '', + '👷🏼‍♀️' => '', + '👷🏽‍♀️' => '', + '👷🏾‍♀️' => '', + '👷🏿‍♀️' => '', + '👳🏻‍♂️' => '', + '👳🏼‍♂️' => '', + '👳🏽‍♂️' => '', + '👳🏾‍♂️' => '', + '👳🏿‍♂️' => '', + '👳🏻‍♀️' => '', + '👳🏼‍♀️' => '', + '👳🏽‍♀️' => '', + '👳🏾‍♀️' => '', + '👳🏿‍♀️' => '', + '🤵🏻‍♂️' => '', + '🤵🏼‍♂️' => '', + '🤵🏽‍♂️' => '', + '🤵🏾‍♂️' => '', + '🤵🏿‍♂️' => '', + '🤵🏻‍♀️' => '', + '🤵🏼‍♀️' => '', + '🤵🏽‍♀️' => '', + '🤵🏾‍♀️' => '', + '🤵🏿‍♀️' => '', + '👰🏻‍♂️' => '', + '👰🏼‍♂️' => '', + '👰🏽‍♂️' => '', + '👰🏾‍♂️' => '', + '👰🏿‍♂️' => '', + '👰🏻‍♀️' => '', + '👰🏼‍♀️' => '', + '👰🏽‍♀️' => '', + '👰🏾‍♀️' => '', + '👰🏿‍♀️' => '', + '🦸🏻‍♂️' => '', + '🦸🏼‍♂️' => '', + '🦸🏽‍♂️' => '', + '🦸🏾‍♂️' => '', + '🦸🏿‍♂️' => '', + '🦸🏻‍♀️' => '', + '🦸🏼‍♀️' => '', + '🦸🏽‍♀️' => '', + '🦸🏾‍♀️' => '', + '🦸🏿‍♀️' => '', + '🦹🏻‍♂️' => '', + '🦹🏼‍♂️' => '', + '🦹🏽‍♂️' => '', + '🦹🏾‍♂️' => '', + '🦹🏿‍♂️' => '', + '🦹🏻‍♀️' => '', + '🦹🏼‍♀️' => '', + '🦹🏽‍♀️' => '', + '🦹🏾‍♀️' => '', + '🦹🏿‍♀️' => '', + '🧙🏻‍♂️' => '', + '🧙🏼‍♂️' => '', + '🧙🏽‍♂️' => '', + '🧙🏾‍♂️' => '', + '🧙🏿‍♂️' => '', + '🧙🏻‍♀️' => '', + '🧙🏼‍♀️' => '', + '🧙🏽‍♀️' => '', + '🧙🏾‍♀️' => '', + '🧙🏿‍♀️' => '', + '🧚🏻‍♂️' => '', + '🧚🏼‍♂️' => '', + '🧚🏽‍♂️' => '', + '🧚🏾‍♂️' => '', + '🧚🏿‍♂️' => '', + '🧚🏻‍♀️' => '', + '🧚🏼‍♀️' => '', + '🧚🏽‍♀️' => '', + '🧚🏾‍♀️' => '', + '🧚🏿‍♀️' => '', + '🧛🏻‍♂️' => '', + '🧛🏼‍♂️' => '', + '🧛🏽‍♂️' => '', + '🧛🏾‍♂️' => '', + '🧛🏿‍♂️' => '', + '🧛🏻‍♀️' => '', + '🧛🏼‍♀️' => '', + '🧛🏽‍♀️' => '', + '🧛🏾‍♀️' => '', + '🧛🏿‍♀️' => '', + '🧜🏻‍♂️' => '', + '🧜🏼‍♂️' => '', + '🧜🏽‍♂️' => '', + '🧜🏾‍♂️' => '', + '🧜🏿‍♂️' => '', + '🧜🏻‍♀️' => '', + '🧜🏼‍♀️' => '', + '🧜🏽‍♀️' => '', + '🧜🏾‍♀️' => '', + '🧜🏿‍♀️' => '', + '🧝🏻‍♂️' => '', + '🧝🏼‍♂️' => '', + '🧝🏽‍♂️' => '', + '🧝🏾‍♂️' => '', + '🧝🏿‍♂️' => '', + '🧝🏻‍♀️' => '', + '🧝🏼‍♀️' => '', + '🧝🏽‍♀️' => '', + '🧝🏾‍♀️' => '', + '🧝🏿‍♀️' => '', + '💆🏻‍♂️' => '', + '💆🏼‍♂️' => '', + '💆🏽‍♂️' => '', + '💆🏾‍♂️' => '', + '💆🏿‍♂️' => '', + '💆🏻‍♀️' => '', + '💆🏼‍♀️' => '', + '💆🏽‍♀️' => '', + '💆🏾‍♀️' => '', + '💆🏿‍♀️' => '', + '💇🏻‍♂️' => '', + '💇🏼‍♂️' => '', + '💇🏽‍♂️' => '', + '💇🏾‍♂️' => '', + '💇🏿‍♂️' => '', + '💇🏻‍♀️' => '', + '💇🏼‍♀️' => '', + '💇🏽‍♀️' => '', + '💇🏾‍♀️' => '', + '💇🏿‍♀️' => '', + '🚶🏻‍♂️' => '', + '🚶🏼‍♂️' => '', + '🚶🏽‍♂️' => '', + '🚶🏾‍♂️' => '', + '🚶🏿‍♂️' => '', + '🚶🏻‍♀️' => '', + '🚶🏼‍♀️' => '', + '🚶🏽‍♀️' => '', + '🚶🏾‍♀️' => '', + '🚶🏿‍♀️' => '', + '🧍🏻‍♂️' => ' 10000 ', + '🧍🏼‍♂️' => '', + '🧍🏽‍♂️' => '', + '🧍🏾‍♂️' => '', + '🧍🏿‍♂️' => '', + '🧍🏻‍♀️' => '', + '🧍🏼‍♀️' => '', + '🧍🏽‍♀️' => '', + '🧍🏾‍♀️' => '', + '🧍🏿‍♀️' => '', + '🧎🏻‍♂️' => '', + '🧎🏼‍♂️' => '', + '🧎🏽‍♂️' => '', + '🧎🏾‍♂️' => '', + '🧎🏿‍♂️' => '', + '🧎🏻‍♀️' => '', + '🧎🏼‍♀️' => '', + '🧎🏽‍♀️' => '', + '🧎🏾‍♀️' => '', + '🧎🏿‍♀️' => '', + '🏃🏻‍♂️' => '', + '🏃🏼‍♂️' => '', + '🏃🏽‍♂️' => '', + '🏃🏾‍♂️' => '', + '🏃🏿‍♂️' => '', + '🏃🏻‍♀️' => '', + '🏃🏼‍♀️' => '', + '🏃🏽‍♀️' => '', + '🏃🏾‍♀️' => '', + '🏃🏿‍♀️' => '', + '🧖🏻‍♂️' => '', + '🧖🏼‍♂️' => '', + '🧖🏽‍♂️' => '', + '🧖🏾‍♂️' => '', + '🧖🏿‍♂️' => '', + '🧖🏻‍♀️' => '', + '🧖🏼‍♀️' => '', + '🧖🏽‍♀️' => '', + '🧖🏾‍♀️' => '', + '🧖🏿‍♀️' => '', + '🧗🏻‍♂️' => '', + '🧗🏼‍♂️' => '', + '🧗🏽‍♂️' => '', + '🧗🏾‍♂️' => '', + '🧗🏿‍♂️' => '', + '🧗🏻‍♀️' => '', + '🧗🏼‍♀️' => '', + '🧗🏽‍♀️' => '', + '🧗🏾‍♀️' => '', + '🧗🏿‍♀️' => '', + '🏌️‍♂️' => '', + '🏌🏻‍♂️' => '', + '🏌🏼‍♂️' => '', + '🏌🏽‍♂️' => '', + '🏌🏾‍♂️' => '', + '🏌🏿‍♂️' => '', + '🏌️‍♀️' => '', + '🏌🏻‍♀️' => '', + '🏌🏼‍♀️' => '', + '🏌🏽‍♀️' => '', + '🏌🏾‍♀️' => '', + '🏌🏿‍♀️' => '', + '🏄🏻‍♂️' => '', + '🏄🏼‍♂️' => '', + '🏄🏽‍♂️' => '', + '🏄🏾‍♂️' => '', + '🏄🏿‍♂️' => '', + '🏄🏻‍♀️' => '', + '🏄🏼‍♀️' => '', + '🏄🏽‍♀️' => '', + '🏄🏾‍♀️' => '', + '🏄🏿‍♀️' => '', + '🚣🏻‍♂️' => '', + '🚣🏼‍♂️' => '', + '🚣🏽‍♂️' => '', + '🚣🏾‍♂️' => '', + '🚣🏿‍♂️' => '', + '🚣🏻‍♀️' => '', + '🚣🏼‍♀️' => '', + '🚣🏽‍♀️' => '', + '🚣🏾‍♀️' => '', + '🚣🏿‍♀️' => '', + '🏊🏻‍♂️' => '', + '🏊🏼‍♂️' => '', + '🏊🏽‍♂️' => '', + '🏊🏾‍♂️' => '', + '🏊🏿‍♂️' => '', + '🏊🏻‍♀️' => '', + '🏊🏼‍♀️' => '', + '🏊🏽‍♀️' => '', + '🏊🏾‍♀️' => '', + '🏊🏿‍♀️' => '', + '⛹️‍♂️' => '', + '⛹🏻‍♂️' => '', + '⛹🏼‍♂️' => '', + '⛹🏽‍♂️' => '', + '⛹🏾‍♂️' => '', + '⛹🏿‍♂️' => '', + '⛹️‍♀️' => '', + '⛹🏻‍♀️' => '', + '⛹🏼‍♀️' => '', + '⛹🏽‍♀️' => '', + '⛹🏾‍♀️' => '', + '⛹🏿‍♀️' => '', + '🏋️‍♂️' => '', + '🏋🏻‍♂️' => '', + '🏋🏼‍♂️' => '', + '🏋🏽‍♂️' => '', + '🏋🏾‍♂️' => '', + '🏋🏿‍♂️' => '', + '🏋️‍♀️' => '', + '🏋🏻‍♀️' => '', + '🏋🏼‍♀️' => '', + '🏋🏽‍♀️' => '', + '🏋🏾‍♀️' => '', + '🏋🏿‍♀️' => '', + '🚴🏻‍♂️' => '', + '🚴🏼‍♂️' => '', + '🚴🏽‍♂️' => '', + '🚴🏾‍♂️' => '', + '🚴🏿‍♂️' => '', + '🚴🏻‍♀️' => '', + '🚴🏼‍♀️' => '', + '🚴🏽‍♀️' => '', + '🚴🏾‍♀️' => '', + '🚴🏿‍♀️' => '', + '🚵🏻‍♂️' => '', + '🚵🏼‍♂️' => '', + '🚵🏽‍♂️' => '', + '🚵🏾‍♂️' => '', + '🚵🏿‍♂️' => '', + '🚵🏻‍♀️' => '', + '🚵🏼‍♀️' => '', + '🚵🏽‍♀️' => '', + '🚵🏾‍♀️' => '', + '🚵🏿‍♀️' => '', + '🤸🏻‍♂️' => '', + '🤸🏼‍♂️' => '', + '🤸🏽‍♂️' => '', + '🤸🏾‍♂️' => '', + '🤸🏿‍♂️' => '', + '🤸🏻‍♀️' => '', + '🤸🏼‍♀️' => '', + '🤸🏽‍♀️' => '', + '🤸🏾‍♀️' => '', + '🤸🏿‍♀️' => '', + '🤽🏻‍♂️' => '', + '🤽🏼‍♂️' => '', + '🤽🏽‍♂️' => '', + '🤽🏾‍♂️' => '', + '🤽🏿‍♂️' => '', + '🤽🏻‍♀️' => '', + '🤽🏼‍♀️' => '', + '🤽🏽‍♀️' => '', + '🤽🏾‍♀️' => '', + '🤽🏿‍♀️' => '', + '🤾🏻‍♂️' => '', + '🤾🏼‍♂️' => '', + '🤾🏽‍♂️' => '', + '🤾🏾‍♂️' => '', + '🤾🏿‍♂️' => '', + '🤾🏻‍♀️' => '', + '🤾🏼‍♀️' => '', + '🤾🏽‍♀️' => '', + '🤾🏾‍♀️' => '', + '🤾🏿‍♀️' => '', + '🤹🏻‍♂️' => '', + '🤹🏼‍♂️' => '', + '🤹🏽‍♂️' => '', + '🤹🏾‍♂️' => '', + '🤹🏿‍♂️' => '', + '🤹🏻‍♀️' => '', + '🤹🏼‍♀️' => '', + '🤹🏽‍♀️' => '', + '🤹🏾‍♀️' => '', + '🤹🏿‍♀️' => '', + '🧘🏻‍♂️' => '', + '🧘🏼‍♂️' => '', + '🧘🏽‍♂️' => '', + '🧘🏾‍♂️' => '', + '🧘🏿‍♂️' => '', + '🧘🏻‍♀️' => '', + '🧘🏼‍♀️' => '', + '🧘🏽‍♀️' => '', + '🧘🏾‍♀️' => '', + '🧘🏿‍♀️' => '', + '🧑‍🤝‍🧑' => '', + '👩‍❤‍👨' => '', + '👨‍❤‍👨' => '', + '👩‍❤‍👩' => '', + '👨‍👩‍👦' => '', + '👨‍👩‍👧' => '', + '👨‍👨‍👦' => '', + '👨‍👨‍👧' => '', + '👩‍👩‍👦' => '', + '👩‍👩‍👧' => '', + '👨‍👦‍👦' => '', + '👨‍👧‍👦' => '', + '👨‍👧‍👧' => '', + '👩‍👦‍👦' => '', + '👩‍👧‍👦' => '', + '👩‍👧‍👧' => '', + '🏳️‍⚧️' => '', + '😶‍🌫️' => '', + '❤️‍🔥' => '', + '❤️‍🩹' => '', + '👁‍🗨️' => '', + '👁️‍🗨' => '', + '🧔‍♂️' => '', + '🧔🏻‍♂' => '', + '🧔🏼‍♂' => '', + '🧔🏽‍♂' => '', + '🧔🏾‍♂' => '', + '🧔🏿‍♂' => '', + '🧔‍♀️' => '', + '🧔🏻‍♀' => '', + '🧔🏼‍♀' => '', + '🧔🏽‍♀' => '', + '🧔🏾‍♀' => '', + '🧔🏿‍♀' => '', + '👨🏻‍🦰' => '', + '👨🏼‍🦰' => '', + '👨🏽‍🦰' => '', + '👨🏾‍🦰' => '', + '👨🏿‍🦰' => '', + '👨🏻‍🦱' => '', + '👨🏼‍🦱' => '', + '👨🏽‍🦱' => '', + '👨🏾‍🦱' => '', + '👨🏿‍🦱' => '', + '👨🏻‍🦳' => '', + '👨🏼‍🦳' => '', + '👨🏽‍🦳' => '', + '👨🏾‍🦳' => '', + '👨🏿‍🦳' => '', + '👨🏻‍🦲' => '', + '👨🏼‍🦲' => '', + '👨🏽‍🦲' => '', + '👨🏾‍🦲' => '', + '👨🏿‍🦲' => '', + '👩🏻‍🦰' => '', + '👩🏼‍🦰' => '', + '👩🏽‍🦰' => '', + '👩🏾‍🦰' => '', + '👩🏿‍🦰' => '', + '🧑🏻‍🦰' => '', + '🧑🏼‍🦰' => '', + '🧑🏽‍🦰' => '', + '🧑🏾‍🦰' => '', + '🧑🏿‍🦰' => '', + '👩🏻‍🦱' => '', + '👩🏼‍🦱' => '', + '👩🏽‍🦱' => '', + '👩🏾‍🦱' => '', + '👩🏿‍🦱' => '', + '🧑🏻‍🦱' => '', + '🧑🏼‍🦱' => '', + '🧑🏽‍🦱' => '', + '🧑🏾‍🦱' => '', + '🧑🏿‍🦱' => '', + '👩🏻‍🦳' => '', + '👩🏼‍🦳' => '', + '👩🏽‍🦳' => '', + '👩🏾‍🦳' => '', + '👩🏿‍🦳' => '', + '🧑🏻‍🦳' => '', + '🧑🏼‍🦳' => '', + '🧑🏽‍🦳' => '', + '🧑🏾‍🦳' => '', + '🧑🏿‍🦳' => '', + '👩🏻‍🦲' => '', + '👩🏼‍🦲' => '', + '👩🏽‍🦲' => '', + '👩🏾‍🦲' => '', + '👩🏿‍🦲' => '', + '🧑🏻‍🦲' => '', + '🧑🏼‍🦲' => '', + '🧑🏽‍🦲' => '', + '🧑🏾‍🦲' => '', + '🧑🏿‍🦲' => '', + '👱‍♀️' => '', + '👱🏻‍♀' => '', + '👱🏼‍♀' => '', + '👱🏽‍♀' => '', + '👱🏾‍♀' => '', + '👱🏿‍♀' => '', + '👱‍♂️' => '', + '👱🏻‍♂' => '', + '👱🏼‍♂' => '', + '👱🏽‍♂' => '', + '👱🏾‍♂' => '', + '👱🏿‍♂' => '', + '🙍‍♂️' => '', + '🙍🏻‍♂' => '', + '🙍🏼‍♂' => '', + '🙍🏽‍♂' => '', + '🙍🏾‍♂' => '', + '🙍🏿‍♂' => '', + '🙍‍♀️' => '', + '🙍🏻‍♀' => '', + '🙍🏼‍♀' => '', + '🙍🏽‍♀' => '', + '🙍🏾‍♀' => '', + '🙍🏿‍♀' => '', + '🙎‍♂️' => '', + '🙎🏻‍♂' => '', + '🙎🏼‍♂' => '', + '🙎🏽‍♂' => '', + '🙎🏾‍♂' => '', + '🙎🏿‍♂' => '', + '🙎‍♀️' => '', + '🙎🏻‍♀' => '', + '🙎🏼‍♀' => '', + '🙎🏽‍♀' => '', + '🙎🏾‍♀' => '', + '🙎🏿‍♀' => '', + '🙅‍♂️' => '', + '🙅🏻‍♂' => '', + '🙅🏼‍♂' => '', + '🙅🏽‍♂' => '', + '🙅🏾‍♂' => '', + '🙅🏿‍♂' => '', + '🙅‍♀️' => '', + '🙅🏻‍♀' => '', + '🙅🏼‍♀' => '', + '🙅🏽‍♀' => '', + '🙅🏾‍♀' => '', + '🙅🏿‍♀' => '', + '🙆‍♂️' => '', + '🙆🏻‍♂' => '', + '🙆🏼‍♂' => '', + '🙆🏽‍♂' => '', + '🙆🏾‍♂' => '', + '🙆🏿‍♂' => '', + '🙆‍♀️' => '', + '🙆🏻‍♀' => '', + '🙆🏼‍♀' => '', + '🙆🏽‍♀' => '', + '🙆🏾‍♀' => '', + '🙆🏿‍♀' => '', + '💁‍♂️' => '', + '💁🏻‍♂' => '', + '💁🏼‍♂' => '', + '💁🏽‍♂' => '', + '💁🏾‍♂' => '', + '💁🏿‍♂' => '', + '💁‍♀️' => '', + '💁🏻‍♀' => '', + '💁🏼‍♀' => '', + '💁🏽‍♀' => '', + '💁🏾‍♀' => '', + '💁🏿‍♀' => '', + '🙋‍♂️' => '', + '🙋🏻‍♂' => '', + '🙋🏼‍♂' => '', + '🙋🏽‍♂' => '', + '🙋🏾‍♂' => '', + '🙋🏿‍♂' => '', + '🙋‍♀️' => '', + '🙋🏻‍♀' => '', + '🙋🏼‍♀' => '', + '🙋🏽‍♀' => '', + '🙋🏾‍♀' => '', + '🙋🏿‍♀' => '', + '🧏‍♂️' => '', + '🧏🏻‍♂' => '', + '🧏🏼‍♂' => '', + '🧏🏽‍♂' => '', + '🧏🏾‍♂' => '', + '🧏🏿‍♂' => '', + '🧏‍♀️' => '', + '🧏🏻‍♀' => '', + '🧏🏼‍♀' => '', + '🧏🏽‍♀' => '', + '🧏🏾‍♀' => '', + '🧏🏿‍♀' => '', + '🙇‍♂️' => '', + '🙇🏻‍♂' => '', + '🙇🏼‍♂' => '', + '🙇🏽‍♂' => '', + '🙇🏾‍♂' => '', + '🙇🏿‍♂' => '', + '🙇‍♀️' => '', + '🙇🏻‍♀' => '', + '🙇🏼‍♀' => '', + '🙇🏽‍♀' => '', + '🙇🏾‍♀' => '', + '🙇🏿‍♀' => '', + '🤦‍♂️' => '', + '🤦🏻‍♂' => '', + '🤦🏼‍♂' => '', + '🤦🏽‍♂' => '', + '🤦🏾‍♂' => '', + '🤦🏿‍♂' => '', + '🤦‍♀️' => '', + '🤦🏻‍♀' => '', + '🤦🏼‍♀' => '', + '🤦🏽‍♀' => '', + '🤦🏾‍♀' => '', + '🤦🏿‍♀' => '', + '🤷‍♂️' => '', + '🤷🏻‍♂' => '', + '🤷🏼‍♂' => '', + '🤷🏽‍♂' => '', + '🤷🏾‍♂' => '', + '🤷🏿‍♂' => '', + '🤷‍♀️' => '', + '🤷🏻‍♀' => '', + '🤷🏼‍♀' => '', + '🤷🏽‍♀' => '', + '🤷🏾‍♀' => '', + '🤷🏿‍♀' => '', + '🧑‍⚕️' => '', + '🧑🏻‍⚕' => '', + '🧑🏼‍⚕' => '', + '🧑🏽‍⚕' => '', + '🧑🏾‍⚕' => '', + '🧑🏿‍⚕' => '', + '👨‍⚕️' => '', + '👨🏻‍⚕' => '', + '👨🏼‍⚕' => '', + '👨🏽‍⚕' => '', + '👨🏾‍⚕' => '', + '👨🏿‍⚕' => '', + '👩‍⚕️' => '', + '👩🏻‍⚕' => '', + '👩🏼‍⚕' => '', + '👩🏽‍⚕' => '', + '👩🏾‍⚕' => '', + '👩🏿‍⚕' => '', + '🧑🏻‍🎓' => '', + '🧑🏼‍🎓' => '', + '🧑🏽‍🎓' => '', + '🧑🏾‍🎓' => '', + '🧑🏿‍🎓' => '', + '👨🏻‍🎓' => '', + '👨🏼‍🎓' => '', + '👨🏽‍🎓' => '', + '👨🏾‍🎓' => '', + '👨🏿‍🎓' => '', + '👩🏻‍🎓' => '', + '👩🏼‍🎓' => '', + '👩🏽‍🎓' => '', + '👩🏾‍🎓' => '', + '👩🏿‍🎓' => '', + '🧑🏻‍🏫' => '', + '🧑🏼‍🏫' => '', + '🧑🏽‍🏫' => '', + '🧑🏾‍🏫' => '', + '🧑🏿‍🏫' => '', + '👨🏻‍🏫' => '', + '👨🏼‍🏫' => '', + '👨🏽‍🏫' => '', + '👨🏾‍🏫' => '', + '👨🏿‍🏫' => '', + '👩🏻‍🏫' => '', + '👩🏼‍🏫' => '', + '👩🏽‍🏫' => '', + '👩🏾‍🏫' => '', + '👩🏿‍🏫' => '', + '🧑‍⚖️' => '', + '🧑🏻‍⚖' => '', + '🧑🏼‍⚖' => '', + '🧑🏽‍⚖' => '', + '🧑🏾‍⚖' => '', + '🧑🏿‍⚖' => '', + '👨‍⚖️' => '', + '👨🏻‍⚖' => '', + '👨🏼‍⚖' => '', + '👨🏽‍⚖' => '', + '👨🏾‍⚖' => '', + '👨🏿‍⚖' => '', + '👩‍⚖️' => '', + '👩🏻‍⚖' => '', + '👩🏼‍⚖' => '', + '👩🏽‍⚖' => '', + '👩🏾‍⚖' => '', + '👩🏿‍⚖' => '', + '🧑🏻‍🌾' => '', + '🧑🏼‍🌾' => '', + '🧑🏽‍🌾' => '', + '🧑🏾‍🌾' => '', + '🧑🏿‍🌾' => '', + '👨🏻‍🌾' => '', + '👨🏼‍🌾' => '', + '👨🏽‍🌾' => '', + '👨🏾‍🌾' => '', + '👨🏿‍🌾' => '', + '👩🏻‍🌾' => '', + '👩🏼‍🌾' => '', + '👩🏽‍🌾' => '', + '👩🏾‍🌾' => '', + '👩🏿‍🌾' => '', + '🧑🏻‍🍳' => '', + '🧑🏼‍🍳' => '', + '🧑🏽‍🍳' => '', + '🧑🏾‍🍳' => '', + '🧑🏿‍🍳' => '', + '👨🏻‍🍳' => '', + '👨🏼‍🍳' => '', + '👨🏽‍🍳' => '', + '👨🏾‍🍳' => '', + '👨🏿‍🍳' => '', + '👩🏻‍🍳' => '', + '👩🏼‍🍳' => '', + '👩🏽‍🍳' => '', + '👩🏾‍🍳' => '', + '👩🏿‍🍳' => '', + '🧑🏻‍🔧' => '', + '🧑🏼‍🔧' => '', + '🧑🏽‍🔧' => '', + '🧑🏾‍🔧' => '', + '🧑🏿‍🔧' => '', + '👨🏻‍🔧' => '', + '👨🏼‍🔧' => '', + '👨🏽‍🔧' => '', + '👨🏾‍🔧' => '', + '👨🏿‍🔧' => '', + '👩🏻‍🔧' => '', + '👩🏼‍🔧' => '', + '👩🏽‍🔧' => '', + '👩🏾‍🔧' => '', + '👩🏿‍🔧' => '', + '🧑🏻‍🏭' => '', + '🧑🏼‍🏭' => '', + '🧑🏽‍🏭' => '', + '🧑🏾‍🏭' => '', + '🧑🏿‍🏭' => '', + '👨🏻‍🏭' => '', + '👨🏼‍🏭' => '', + '👨🏽‍🏭' => '', + '👨🏾‍🏭' => '', + '👨🏿‍🏭' => '', + '👩🏻‍🏭' => '', + '👩🏼‍🏭' => '', + '👩🏽‍🏭' => '', + '👩🏾‍🏭' => '', + '👩🏿‍🏭' => '', + '🧑🏻‍💼' => '', + '🧑🏼‍💼' => '', + '🧑🏽‍💼' => '', + '🧑🏾‍💼' => '', + '🧑🏿‍💼' => '', + '👨🏻‍💼' => '', + '👨🏼‍💼' => '', + '👨🏽‍💼' => '', + '👨🏾‍💼' => '', + '👨🏿‍💼' => '', + '👩🏻‍💼' => '', + '👩🏼‍💼' => '', + '👩🏽‍💼' => '', + '👩🏾‍💼' => '', + '👩🏿‍💼' => '', + '🧑🏻‍🔬' => '', + '🧑🏼‍🔬' => '', + '🧑🏽‍🔬' => '', + '🧑🏾‍🔬' => '', + '🧑🏿‍🔬' => '', + '👨🏻‍🔬' => '', + '👨🏼‍🔬' => '', + '👨🏽‍🔬' => '', + '👨🏾‍🔬' => '', + '👨🏿‍🔬' => '', + '👩🏻‍🔬' => '', + '👩🏼‍🔬' => '', + '👩🏽‍🔬' => '', + '👩🏾‍🔬' => '', + '👩🏿‍🔬' => '', + '🧑🏻‍💻' => '', + '🧑🏼‍💻' => '', + '🧑🏽‍💻' => '', + '🧑🏾‍💻' => '', + '🧑🏿‍💻' => '', + '👨🏻‍💻' => '', + '👨🏼‍💻' => '', + '👨🏽‍💻' => '', + '👨🏾‍💻' => '', + '👨🏿‍💻' => '', + '👩🏻‍💻' => '', + '👩🏼‍💻' => '', + '👩🏽‍💻' => '', + '👩🏾‍💻' => '', + '👩🏿‍💻' => '', + '🧑🏻‍🎤' => '', + '🧑🏼‍🎤' => '', + '🧑🏽‍🎤' => '', + '🧑🏾‍🎤' => '', + '🧑🏿‍🎤' => '', + '👨🏻‍🎤' => '', + '👨🏼‍🎤' => '', + '👨🏽‍🎤' => '', + '👨🏾‍🎤' => '', + '👨🏿‍🎤' => '', + '👩🏻‍🎤' => '', + '👩🏼‍🎤' => '', + '👩🏽‍🎤' => '', + '👩🏾‍🎤' => '', + '👩🏿‍🎤' => '', + '🧑🏻‍🎨' => '', + '🧑🏼‍🎨' => '', + '🧑🏽‍🎨' => '', + '🧑🏾‍🎨' => '', + '🧑🏿‍🎨' => '', + '👨🏻‍🎨' => '', + '👨🏼‍🎨' => '', + '👨🏽‍🎨' => '', + '👨🏾‍🎨' => '', + '👨🏿‍🎨' => '', + '👩🏻‍🎨' => '', + '👩🏼‍🎨' => '', + '👩🏽‍🎨' => '', + '👩🏾‍🎨' => '', + '👩🏿‍🎨' => '', + '🧑‍✈️' => '', + '🧑🏻‍✈' => '', + '🧑🏼‍✈' => '', + '🧑🏽‍✈' => '', + '🧑🏾‍✈' => '', + '🧑🏿‍✈' => '', + '👨‍✈️' => '', + '👨🏻‍✈' => '', + '👨🏼‍✈' => '', + '👨🏽‍✈' => '', + '👨🏾‍✈' => '', + '👨🏿‍✈' => '', + '👩‍✈️' => '', + '👩🏻‍✈' => '', + '👩🏼‍✈' => '', + '👩🏽‍✈' => '', + '👩🏾‍✈' => '', + '👩🏿‍✈' => '', + '🧑🏻‍🚀' => '', + '🧑🏼‍🚀' => '', + '🧑🏽‍🚀' => '', + '🧑🏾‍🚀' => '', + '🧑🏿‍🚀' => '', + '👨🏻‍🚀' => '', + '👨🏼‍🚀' => '', + '👨🏽‍🚀' => '', + '👨🏾‍🚀' => '', + '👨🏿‍🚀' => '', + '👩🏻‍🚀' => '', + '👩🏼‍🚀' => '', + '👩🏽‍🚀' => '', + '👩🏾‍🚀' => '', + '👩🏿‍🚀' => '', + '🧑🏻‍🚒' => '', + '🧑🏼‍🚒' => '', + '🧑🏽‍🚒' => '', + '🧑🏾‍🚒' => '', + '🧑🏿‍🚒' => '', + '👨🏻‍🚒' => '', + '👨🏼‍🚒' => '', + '👨🏽‍🚒' => '', + '👨🏾‍🚒' => '', + '👨🏿‍🚒' => '', + '👩🏻‍🚒' => '', + '👩🏼‍🚒' => '', + '👩🏽‍🚒' => '', + '👩🏾‍🚒' => '', + '👩🏿‍🚒' => '', + '👮‍♂️' => '', + '👮🏻‍♂' => '', + '👮🏼‍♂' => '', + '👮🏽‍♂' => '', + '👮🏾‍♂' => '', + '👮🏿‍♂' => '', + '👮‍♀️' => '', + '👮🏻‍♀' => '', + '👮🏼‍♀' => '', + '👮🏽‍♀' => '', + '👮🏾‍♀' => '', + '👮🏿‍♀' => '', + '🕵‍♂️' => '', + '🕵️‍♂' => '', + '🕵🏻‍♂' => '', + '🕵🏼‍♂' => '', + '🕵🏽‍♂' => '', + '🕵🏾‍♂' => '', + '🕵🏿‍♂' => '', + '🕵‍♀️' => '', + '🕵️‍♀' => '', + '🕵🏻‍♀' => '', + '🕵🏼‍♀' => '', + '🕵🏽‍♀' => '', + '🕵🏾‍♀' => '', + '🕵🏿‍♀' => '', + '💂‍♂️' => '', + '💂🏻‍♂' => '', + '💂🏼‍♂' => '', + '💂🏽‍♂' => '', + '💂🏾‍♂' => '', + '💂🏿‍♂' => '', + '💂‍♀️' => '', + '💂🏻‍♀' => '', + '💂🏼‍♀' => '', + '💂🏽‍♀' => '', + '💂🏾‍♀' => '', + '💂🏿‍♀' => '', + '👷‍♂️' => '', + '👷🏻‍♂' => '', + '👷🏼‍♂' => '', + '👷🏽‍♂' => '', + '👷🏾‍♂' => '', + '👷🏿‍♂' => '', + '👷‍♀️' => '', + '👷🏻‍♀' => '', + '👷🏼‍♀' => '', + '👷🏽‍♀' => '', + '👷🏾‍♀' => '', + '👷🏿‍♀' => '', + '👳‍♂️' => '', + '👳🏻‍♂' => '', + '👳🏼‍♂' => '', + '👳🏽‍♂' => '', + '👳🏾‍♂' => '', + '👳🏿‍♂' => '', + '👳‍♀️' => '', + '👳🏻‍♀' => '', + '👳🏼‍♀' => '', + '👳🏽‍♀' => '', + '👳🏾‍♀' => '', + '👳🏿‍♀' => '', + '🤵‍♂️' => '', + '🤵🏻‍♂' => '', + '🤵🏼‍♂' => '', + '🤵🏽‍♂' => '', + '🤵🏾‍♂' => '', + '🤵🏿‍♂' => '', + '🤵‍♀️' => '', + '🤵🏻‍♀' => '', + '🤵🏼‍♀' => '', + '🤵🏽‍♀' => '', + '🤵🏾‍♀' => '', + '🤵🏿‍♀' => '', + '👰‍♂️' => '', + '👰🏻‍♂' => '', + '👰🏼‍♂' => '', + '👰🏽‍♂' => '', + '👰🏾‍♂' => '', + '👰🏿‍♂' => '', + '👰‍♀️' => '', + '👰🏻‍♀' => '', + '👰🏼‍♀' => '', + '👰🏽‍♀' => '', + '👰🏾‍♀' => '', + '👰🏿‍♀' => '', + '👩🏻‍🍼' => '', + '👩🏼‍🍼' => '', + '👩🏽‍🍼' => '', + '👩🏾‍🍼' => '', + '👩🏿‍🍼' => '', + '👨🏻‍🍼' => '', + '👨🏼‍🍼' => '', + '👨🏽‍🍼' => '', + '👨🏾‍🍼' => '', + '👨🏿‍🍼' => '', + '🧑🏻‍🍼' => '', + '🧑🏼‍🍼' => '', + '🧑🏽‍🍼' => '', + '🧑🏾‍🍼' => '', + '🧑🏿‍🍼' => '', + '🧑🏻‍🎄' => '', + '🧑🏼‍🎄' => '', + '🧑🏽‍🎄' => '', + '🧑🏾‍🎄' => '', + '🧑🏿‍🎄' => '', + '🦸‍♂️' => '', + '🦸🏻‍♂' => '', + '🦸🏼‍♂' => '', + '🦸🏽‍♂' => '', + '🦸🏾‍♂' => '', + '🦸🏿‍♂' => '', + '🦸‍♀️' => '', + '🦸🏻‍♀' => '', + '🦸🏼‍♀' => '', + '🦸🏽‍♀' => '', + '🦸🏾‍♀' => '', + '🦸🏿‍♀' => '', + '🦹‍♂️' => '', + '🦹🏻‍♂' => '', + '🦹🏼‍♂' => '', + '🦹🏽‍♂' => '', + '🦹🏾‍♂' => '', + '🦹🏿‍♂' => '', + '🦹‍♀️' => '', + '🦹🏻‍♀' => '', + '🦹🏼‍♀' => '', + '🦹🏽‍♀' => '', + '🦹🏾‍♀' => '', + '🦹🏿‍♀' => '', + '🧙‍♂️' => '', + '🧙🏻‍♂' => '', + '🧙🏼‍♂' => '', + '🧙🏽‍♂' => '', + '🧙🏾‍♂' => '', + '🧙🏿‍♂' => '', + '🧙‍♀️' => '', + '🧙🏻‍♀' => '', + '🧙🏼‍♀' => '', + '🧙🏽‍♀' => '', + '🧙🏾‍♀' => '', + '🧙🏿‍♀' => '', + '🧚‍♂️' => '', + '🧚🏻‍♂' => '', + '🧚🏼‍♂' => '', + '🧚🏽‍♂' => '', + '🧚🏾‍♂' => '', + '🧚🏿‍♂' => '', + '🧚‍♀️' => '', + '🧚🏻‍♀' => '', + '🧚🏼‍♀' => '', + '🧚🏽‍♀' => '', + '🧚🏾‍♀' => '', + '🧚🏿‍♀' => '', + '🧛‍♂️' => '', + '🧛🏻‍♂' => '', + '🧛🏼‍♂' => '', + '🧛🏽‍♂' => '', + '🧛🏾‍♂' => '', + '🧛🏿‍♂' => '', + '🧛‍♀️' => '', + '🧛🏻‍♀' => '', + '🧛🏼‍♀' => '', + '🧛🏽‍♀' => '', + '🧛🏾‍♀' => '', + '🧛🏿‍♀' => '', + '🧜‍♂️' => '', + '🧜🏻‍♂' => '', + '🧜🏼‍♂' => '', + '🧜🏽‍♂' => '', + '🧜🏾‍♂' => '', + '🧜🏿‍♂' => '', + '🧜‍♀️' => '', + '🧜🏻‍♀' => '', + '🧜🏼‍♀' => '', + '🧜🏽‍♀' => '', + '🧜🏾‍♀' => '', + '🧜🏿‍♀' => '', + '🧝‍♂️' => '', + '🧝🏻‍♂' => '', + '🧝🏼‍♂' => '', + '🧝🏽‍♂' => '', + '🧝🏾‍♂' => '', + '🧝🏿‍♂' => '', + '🧝‍♀️' => '', + '🧝🏻‍♀' => '', + '🧝🏼‍♀' => '', + '🧝🏽‍♀' => '', + '🧝🏾‍♀' => '', + '🧝🏿‍♀' => '', + '🧞‍♂️' => '', + '🧞‍♀️' => '', + '🧟‍♂️' => '', + '🧟‍♀️' => '', + '💆‍♂️' => '', + '💆🏻‍♂' => '', + '💆🏼‍♂' => '', + '💆🏽‍♂' => '', + '💆🏾‍♂' => '', + '💆🏿‍♂' => '', + '💆‍♀️' => '', + '💆🏻‍♀' => '', + '💆🏼‍♀' => '', + '💆🏽‍♀' => '', + '💆🏾‍♀' => '', + '💆🏿‍♀' => '', + '💇‍♂️' => '', + '💇🏻‍♂' => '', + '💇🏼‍♂' => '', + '💇🏽‍♂' => '', + '💇🏾‍♂' => '', + '💇🏿‍♂' => '', + '💇‍♀️' => '', + '💇🏻‍♀' => '', + '💇🏼‍♀' => '', + '💇🏽‍♀' => '', + '💇🏾‍♀' => '', + '💇🏿‍♀' => '', + '🚶‍♂️' => '', + '🚶🏻‍♂' => '', + '🚶🏼‍♂' => '', + '🚶🏽‍♂' => '', + '🚶🏾‍♂' => '', + '🚶🏿‍♂' => '', + '🚶‍♀️' => '', + '🚶🏻‍♀' => '', + '🚶🏼‍♀' => '', + '🚶🏽‍♀' => '', + '🚶🏾‍♀' => '', + '🚶🏿‍♀' => '', + '🧍‍♂️' => '', + '🧍🏻‍♂' => '', + '🧍🏼‍♂' => '', + '🧍🏽‍♂' => '', + '🧍🏾‍♂' => '', + '🧍🏿‍♂' => '', + '🧍‍♀️' => '', + '🧍🏻‍♀' => '', + '🧍🏼‍♀' => '', + '🧍🏽‍♀' => '', + '🧍🏾‍♀' => '', + '🧍🏿‍♀' => '', + '🧎‍♂️' => '', + '🧎🏻‍♂' => '', + '🧎🏼‍♂' => '', + '🧎🏽‍♂' => '', + '🧎🏾‍♂' => '', + '🧎🏿‍♂' => '', + '🧎‍♀️' => '', + '🧎🏻‍♀' => '', + '🧎🏼‍♀' => '', + '🧎🏽‍♀' => '', + '🧎🏾‍♀' => '', + '🧎🏿‍♀' => '', + '🧑🏻‍🦯' => '', + '🧑🏼‍🦯' => '', + '🧑🏽‍🦯' => '', + '🧑🏾‍🦯' => '', + '🧑🏿‍🦯' => '', + '👨🏻‍🦯' => '', + '👨🏼‍🦯' => '', + '👨🏽‍🦯' => '', + '👨🏾‍🦯' => '', + '👨🏿‍🦯' => '', + '👩🏻‍🦯' => '', + '👩🏼‍🦯' => '', + '👩🏽‍🦯' => '', + '👩🏾‍🦯' => '', + '👩🏿‍🦯' => '', + '🧑🏻‍🦼' => '', + '🧑🏼‍🦼' => '', + '🧑🏽‍🦼' => '', + '🧑🏾‍🦼' => '', + '🧑🏿‍🦼' => '', + '👨🏻‍🦼' => '', + '👨🏼‍🦼' => '', + '👨🏽‍🦼' => '', + '👨🏾‍🦼' => '', + '👨🏿‍🦼' => '', + '👩🏻‍🦼' => '', + '👩🏼‍🦼' => '', + '👩🏽‍🦼' => '', + '👩🏾‍🦼' => '', + '👩🏿‍🦼' => '', + '🧑🏻‍🦽' => '', + '🧑🏼‍🦽' => '', + '🧑🏽‍🦽' => '', + '🧑🏾‍🦽' => '', + '🧑🏿‍🦽' => '', + '👨🏻‍🦽' => '', + '👨🏼‍🦽' => '', + '👨🏽‍🦽' => '', + '👨🏾‍🦽' => '', + '👨🏿‍🦽' => '', + '👩🏻‍🦽' => '', + '👩🏼‍🦽' => '', + '👩🏽‍🦽' => '', + '👩🏾‍🦽' => '', + '👩🏿‍🦽' => '', + '🏃‍♂️' => '', + '🏃🏻‍♂' => '', + '🏃🏼‍♂' => '', + '🏃🏽‍♂' => '', + '🏃🏾‍♂' => '', + '🏃🏿‍♂' => '', + '🏃‍♀️' => '', + '🏃🏻‍♀' => '', + '🏃🏼‍♀' => '', + '🏃🏽‍♀' => '', + '🏃🏾‍♀' => '', + '🏃🏿‍♀' => '', + '👯‍♂️' => '', + '👯‍♀️' => '', + '🧖‍♂️' => '', + '🧖🏻‍♂' => '', + '🧖🏼‍♂' => '', + '🧖🏽‍♂' => '', + '🧖🏾‍♂' => '', + '🧖🏿‍♂' => '', + '🧖‍♀️' => '', + '🧖🏻‍♀' => '', + '🧖🏼‍♀' => '', + '🧖🏽‍♀' => '', + '🧖🏾‍♀' => '', + '🧖🏿‍♀' => '', + '🧗‍♂️' => '', + '🧗🏻‍♂' => '', + '🧗🏼‍♂' => '', + '🧗🏽‍♂' => '', + '🧗🏾‍♂' => '', + '🧗🏿‍♂' => '', + '🧗‍♀️' => '', + '🧗🏻‍♀' => '', + '🧗🏼‍♀' => '', + '🧗🏽‍♀' => '', + '🧗🏾‍♀' => '', + '🧗🏿‍♀' => '', + '🏌‍♂️' => '', + '🏌️‍♂' => '', + '🏌🏻‍♂' => '', + '🏌🏼‍♂' => '', + '🏌🏽‍♂' => '', + '🏌🏾‍♂' => '', + '🏌🏿‍♂' => '', + '🏌‍♀️' => '', + '🏌️‍♀' => '', + '🏌🏻‍♀' => '', + '🏌🏼‍♀' => '', + '🏌🏽‍♀' => '', + '🏌🏾‍♀' => '', + '🏌🏿‍♀' => '', + '🏄‍♂️' => '', + '🏄🏻‍♂' => '', + '🏄🏼‍♂' => '', + '🏄🏽‍♂' => '', + '🏄🏾‍♂' => '', + '🏄🏿‍♂' => '', + '🏄‍♀️' => '', + '🏄🏻‍♀' => '', + '🏄🏼‍♀' => '', + '🏄🏽‍♀' => '', + '🏄🏾‍♀' => '', + '🏄🏿‍♀' => '', + '🚣‍♂️' => '', + '🚣🏻‍♂' => '', + '🚣🏼‍♂' => '', + '🚣🏽‍♂' => '', + '🚣🏾‍♂' => '', + '🚣🏿‍♂' => '', + '🚣‍♀️' => '', + '🚣🏻‍♀' => '', + '🚣🏼‍♀' => '', + '🚣🏽‍♀' => '', + '🚣🏾‍♀' => '', + '🚣🏿‍♀' => '', + '🏊‍♂️' => '', + '🏊🏻‍♂' => '', + '🏊🏼‍♂' => '', + '🏊🏽‍♂' => '', + '🏊🏾‍♂' => '', + '🏊🏿‍♂' => '', + '🏊‍♀️' => '', + '🏊🏻‍♀' => '', + '🏊🏼‍♀' => '', + '🏊🏽‍♀' => '', + '🏊🏾‍♀' => '', + '🏊🏿‍♀' => '', + '⛹‍♂️' => '', + '⛹️‍♂' => '', + '⛹🏻‍♂' => '', + '⛹🏼‍♂' => '', + '⛹🏽‍♂' => '', + '⛹🏾‍♂' => '', + '⛹🏿‍♂' => '', + '⛹‍♀️' => '', + '⛹️‍♀' => '', + '⛹🏻‍♀' => '', + '⛹🏼‍♀' => '', + '⛹🏽‍♀' => '', + '⛹🏾‍♀' => '', + '⛹🏿‍♀' => '', + '🏋‍♂️' => '', + '🏋️‍♂' => '', + '🏋🏻‍♂' => '', + '🏋🏼‍♂' => '', + '🏋🏽‍♂' => '', + '🏋🏾‍♂' => '', + '🏋🏿‍♂' => '', + '🏋‍♀️' => '', + '🏋️‍♀' => '', + '🏋🏻‍♀' => '', + '🏋🏼‍♀' => '', + '🏋🏽‍♀' => '', + '🏋🏾‍♀' => '', + '🏋🏿‍♀' => '', + '🚴‍♂️' => '', + '🚴🏻‍♂' => '', + '🚴🏼‍♂' => '', + '🚴🏽‍♂' => '', + '🚴🏾‍♂' => '', + '🚴🏿‍♂' => '', + '🚴‍♀️' => '', + '🚴🏻‍♀' => '', + '🚴🏼‍♀' => '', + '🚴🏽‍♀' => '', + '🚴🏾‍♀' => '', + '🚴🏿‍♀' => '', + '🚵‍♂️' => '', + '🚵🏻‍♂' => '', + '🚵🏼‍♂' => '', + '🚵🏽‍♂' => '', + '🚵🏾‍♂' => '', + '🚵🏿‍♂' => '', + '🚵‍♀️' => '', + '🚵🏻‍♀' => '', + '🚵🏼‍♀' => '', + '🚵🏽‍♀' => '', + '🚵🏾‍♀' => '', + '🚵🏿‍♀' => '', + '🤸‍♂️' => '', + '🤸🏻‍♂' => '', + '🤸🏼‍♂' => '', + '🤸🏽‍♂' => '', + '🤸🏾‍♂' => '', + '🤸🏿‍♂' => '', + '🤸‍♀️' => '', + '🤸🏻‍♀' => '', + '🤸🏼‍♀' => '', + '🤸🏽‍♀' => '', + '🤸🏾‍♀' => '', + '🤸🏿‍♀' => '', + '🤼‍♂️' => '', + '🤼‍♀️' => '', + '🤽‍♂️' => '', + '🤽🏻‍♂' => '', + '🤽🏼‍♂' => '', + '🤽🏽‍♂' => '', + '🤽🏾‍♂' => '', + '🤽🏿‍♂' => '', + '🤽‍♀️' => '', + '🤽🏻‍♀' => '', + '🤽🏼‍♀' => '', + '🤽🏽‍♀' => '', + '🤽🏾‍♀' => '', + '🤽🏿‍♀' => '', + '🤾‍♂️' => '', + '🤾🏻‍♂' => '', + '🤾🏼‍♂' => '', + '🤾🏽‍♂' => '', + '🤾🏾‍♂' => '', + '🤾🏿‍♂' => '', + '🤾‍♀️' => '', + '🤾🏻‍♀' => '', + '🤾🏼‍♀' => '', + '🤾🏽‍♀' => '', + '🤾🏾‍♀' => '', + '🤾🏿‍♀' => '', + '🤹‍♂️' => '', + '🤹🏻‍♂' => '', + '🤹🏼‍♂' => '', + '🤹🏽‍♂' => '', + '🤹🏾‍♂' => '', + '🤹🏿‍♂' => '', + '🤹‍♀️' => '', + '🤹🏻‍♀' => '', + '🤹🏼‍♀' => '', + '🤹🏽‍♀' => '', + '🤹🏾‍♀' => '', + '🤹🏿‍♀' => '', + '🧘‍♂️' => '', + '🧘🏻‍♂' => '', + '🧘🏼‍♂' => '', + '🧘🏽‍♂' => '', + '🧘🏾‍♂' => '', + '🧘🏿‍♂' => '', + '🧘‍♀️' => '', + '🧘🏻‍♀' => '', + '🧘🏼‍♀' => '', + '🧘🏽‍♀' => '', + '🧘🏾‍♀' => '', + '🧘🏿‍♀' => '', + '🐻‍❄️' => '', + '🏳️‍🌈' => '', + '🏳‍⚧️' => '', + '🏳️‍⚧' => '', + '🏴‍☠️' => '', + '😶‍🌫' => '', + '😮‍💨' => '', + '😵‍💫' => '', + '❤‍🔥' => '', + '❤‍🩹' => '', + '👁‍🗨' => '', + '🧔‍♂' => '', + '🧔‍♀' => '', + '👨‍🦰' => '', + '👨‍🦱' => '', + '👨‍🦳' => '', + '👨‍🦲' => '', + '👩‍🦰' => '', + '🧑‍🦰' => '', + '👩‍🦱' => '', + '🧑‍🦱' => '', + '👩‍🦳' => '', + '🧑‍🦳' => '', + '👩‍🦲' => '', + '🧑‍🦲' => '', + '👱‍♀' => '', + '👱‍♂' => '', + '🙍‍♂' => '', + '🙍‍♀' => '', + '🙎‍♂' => '', + '🙎‍♀' => '', + '🙅‍♂' => '', + '🙅‍♀' => '', + '🙆‍♂' => '', + '🙆‍♀' => '', + '💁‍♂' => '', + '💁‍♀' => '', + '🙋‍♂' => '', + '🙋‍♀' => '', + '🧏‍♂' => '', + '🧏‍♀' => '', + '🙇‍♂' => '', + '🙇‍♀' => '', + '🤦‍♂' => '', + '🤦‍♀' => '', + '🤷‍♂' => '', + '🤷‍♀' => '', + '🧑‍⚕' => '', + '👨‍⚕' => '', + '👩‍⚕' => '', + '🧑‍🎓' => '', + '👨‍🎓' => '', + '👩‍🎓' => '', + '🧑‍🏫' => '', + '👨‍🏫' => '', + '👩‍🏫' => '', + '🧑‍⚖' => '', + '👨‍⚖' => '', + '👩‍⚖' => '', + '🧑‍🌾' => '', + '👨‍🌾' => '', + '👩‍🌾' => '', + '🧑‍🍳' => '', + '👨‍🍳' => '', + '👩‍🍳' => '', + '🧑‍🔧' => '', + '👨‍🔧' => '', + '👩‍🔧' => '', + '🧑‍🏭' => '', + '👨‍🏭' => '', + '👩‍🏭' => '', + '🧑‍💼' => '', + '👨‍💼' => '', + '👩‍💼' => '', + '🧑‍🔬' => '', + '👨‍🔬' => '', + '👩‍🔬' => '', + '🧑‍💻' => '', + '👨‍💻' => '', + '👩‍💻' => '', + '🧑‍🎤' => '', + '👨‍🎤' => '', + '👩‍🎤' => '', + '🧑‍🎨' => '', + '👨‍🎨' => '', + '👩‍🎨' => '', + '🧑‍✈' => '', + '👨‍✈' => '', + '👩‍✈' => '', + '🧑‍🚀' => '', + '👨‍🚀' => '', + '👩‍🚀' => '', + '🧑‍🚒' => '', + '👨‍🚒' => '', + '👩‍🚒' => '', + '👮‍♂' => '', + '👮‍♀' => '', + '🕵‍♂' => '', + '🕵‍♀' => '', + '💂‍♂' => '', + '💂‍♀' => '', + '👷‍♂' => '', + '👷‍♀' => '', + '👳‍♂' => '', + '👳‍♀' => '', + '🤵‍♂' => '', + '🤵‍♀' => '', + '👰‍♂' => '', + '👰‍♀' => '', + '👩‍🍼' => '', + '👨‍🍼' => '', + '🧑‍🍼' => '', + '🧑‍🎄' => '', + '🦸‍♂' => '', + '🦸‍♀' => '', + '🦹‍♂' => '', + '🦹‍♀' => '', + '🧙‍♂' => '', + '🧙‍♀' => '', + '🧚‍♂' => '', + '🧚‍♀' => '', + '🧛‍♂' => '', + '🧛‍♀' => '', + '🧜‍♂' => '', + '🧜‍♀' => '', + '🧝‍♂' => '', + '🧝‍♀' => '', + '🧞‍♂' => '', + '🧞‍♀' => '', + '🧟‍♂' => '', + '🧟‍♀' => '', + '💆‍♂' => '', + '💆‍♀' => '', + '💇‍♂' => '', + '💇‍♀' => '', + '🚶‍♂' => '', + '🚶‍♀' => '', + '🧍‍♂' => '', + '🧍‍♀' => '', + '🧎‍♂' => '', + '🧎‍♀' => '', + '🧑‍🦯' => '', + '👨‍🦯' => '', + '👩‍🦯' => '', + '🧑‍🦼' => '', + '👨‍🦼' => '', + '👩‍🦼' => '', + '🧑‍🦽' => '', + '👨‍🦽' => '', + '👩‍🦽' => '', + '🏃‍♂' => '', + '🏃‍♀' => '', + '👯‍♂' => '', + '👯‍♀' => '', + '🧖‍♂' => '', + '🧖‍♀' => '', + '🧗‍♂' => '', + '🧗‍♀' => '', + '🏌‍♂' => '', + '🏌‍♀' => '', + '🏄‍♂' => '', + '🏄‍♀' => '', + '🚣‍♂' => '', + '🚣‍♀' => '', + '🏊‍♂' => '', + '🏊‍♀' => '', + '⛹‍♂' => '', + '⛹‍♀' => '', + '🏋‍♂' => '', + '🏋‍♀' => '', + '🚴‍♂' => '', + '🚴‍♀' => '', + '🚵‍♂' => '', + '🚵‍♀' => '', + '🤸‍♂' => '', + '🤸‍♀' => '', + '🤼‍♂' => '', + '🤼‍♀' => '', + '🤽‍♂' => '', + '🤽‍♀' => '', + '🤾‍♂' => '', + '🤾‍♀' => '', + '🤹‍♂' => '', + '🤹‍♀' => '', + '🧘‍♂' => '', + '🧘‍♀' => '', + '👨‍👦' => '', + '👨‍👧' => '', + '👩‍👦' => '', + '👩‍👧' => '', + '🐕‍🦺' => '', + '🐈‍⬛' => '', + '🐻‍❄' => '', + '🐦‍⬛' => '', + '#️⃣' => '', + '*️⃣' => '', + '0️⃣' => '', + '1️⃣' => '', + '2️⃣' => '', + '3️⃣' => '', + '4️⃣' => '', + '5️⃣' => '', + '6️⃣' => '', + '7️⃣' => '', + '8️⃣' => '', + '9️⃣' => '', + '🏳‍🌈' => '', + '🏳‍⚧' => '', + '🏴‍☠' => '', + '☺️' => '', + '☹️' => '', + '☠️' => '', + '❣️' => '', + '❤️' => '', + '🕳️' => '', + '🗨️' => '', + '🗯️' => '', + '👋🏻' => '', + '👋🏼' => '', + '👋🏽' => '', + '👋🏾' => '', + '👋🏿' => '', + '🤚🏻' => '', + '🤚🏼' => '', + '🤚🏽' => '', + '🤚🏾' => '', + '🤚🏿' => '', + '🖐️' => '', + '🖐🏻' => '', + '🖐🏼' => '', + '🖐🏽' => '', + '🖐🏾' => '', + '🖐🏿' => '', + '✋🏻' => '', + '✋🏼' => '', + '✋🏽' => '', + '✋🏾' => '', + '✋🏿' => '', + '🖖🏻' => '', + '🖖🏼' => '', + '🖖🏽' => '', + '🖖🏾' => '', + '🖖🏿' => '', + '🫱🏻' => '', + '🫱🏼' => '', + '🫱🏽' => '', + '🫱🏾' => '', + '🫱🏿' => '', + '🫲🏻' => '', + '🫲🏼' => '', + '🫲🏽' => '', + '🫲🏾' => '', + '🫲🏿' => '', + '🫳🏻' => '', + '🫳🏼' => '', + '🫳🏽' => '', + '🫳🏾' => '', + '🫳🏿' => '', + '🫴🏻' => '', + '🫴🏼' => '', + '🫴🏽' => '', + '🫴🏾' => '', + '🫴🏿' => '', + '👌🏻' => '', + '👌🏼' => '', + '👌🏽' => '', + '👌🏾' => '', + '👌🏿' => '', + '🤌🏻' => '', + '🤌🏼' => '', + '🤌🏽' => '', + '🤌🏾' => '', + '🤌🏿' => '', + '🤏🏻' => '', + '🤏🏼' => '', + '🤏🏽' => '', + '🤏🏾' => '', + '🤏🏿' => '', + '✌️' => '', + '✌🏻' => '', + '✌🏼' => '', + '✌🏽' => '', + '✌🏾' => '', + '✌🏿' => '', + '🤞🏻' => '', + '🤞🏼' => '', + '🤞🏽' => '', + '🤞🏾' => '', + '🤞🏿' => '', + '🫰🏻' => '', + '🫰🏼' => '', + '🫰🏽' => '', + '🫰🏾' => '', + '🫰🏿' => '', + '🤟🏻' => '', + '🤟🏼' => '', + '🤟🏽' => '', + '🤟🏾' => '', + '🤟🏿' => '', + '🤘🏻' => '', + '🤘🏼' => '', + '🤘🏽' => '', + '🤘🏾' => '', + '🤘🏿' => '', + '🤙🏻' => '', + '🤙🏼' => '', + '🤙🏽' => '', + '🤙🏾' => '', + '🤙🏿' => '', + '👈🏻' => '', + '👈🏼' => '', + '👈🏽' => '', + '👈🏾' => '', + '👈🏿' => '', + '👉🏻' => '', + '👉🏼' => '', + '👉🏽' => '', + '👉🏾' => '', + '👉🏿' => '', + '👆🏻' => '', + '👆🏼' => '', + '👆🏽' => '', + '👆🏾' => '', + '👆🏿' => '', + '🖕🏻' => '', + '🖕🏼' => '', + '🖕🏽' => '', + '🖕🏾' => '', + '🖕🏿' => '', + '👇🏻' => '', + '👇🏼' => '', + '👇🏽' => '', + '👇🏾' => '', + '👇🏿' => '', + '☝️' => '', + '☝🏻' => '', + '☝🏼' => '', + '☝🏽' => '', + '☝🏾' => '', + '☝🏿' => '', + '🫵🏻' => '', + '🫵🏼' => '', + '🫵🏽' => '', + '🫵🏾' => '', + '🫵🏿' => '', + '👍🏻' => '', + '👍🏼' => '', + '👍🏽' => '', + '👍🏾' => '', + '👍🏿' => '', + '👎🏻' => '', + '👎🏼' => '', + '👎🏽' => '', + '👎🏾' => '', + '👎🏿' => '', + '✊🏻' => '', + '✊🏼' => '', + '✊🏽' => '', + '✊🏾' => '', + '✊🏿' => '', + '👊🏻' => '', + '👊🏼' => '', + '👊🏽' => '', + '👊🏾' => '', + '👊🏿' => '', + '🤛🏻' => '', + '🤛🏼' => '', + '🤛🏽' => '', + '🤛🏾' => '', + '🤛🏿' => '', + '🤜🏻' => '', + '🤜🏼' => '', + '🤜🏽' => '', + '🤜🏾' => '', + '🤜🏿' => '', + '👏🏻' => '', + '👏🏼' => '', + '👏🏽' => '', + '👏🏾' => '', + '👏🏿' => '', + '🙌🏻' => '', + '🙌🏼' => '', + '🙌🏽' => '', + '🙌🏾' => '', + '🙌🏿' => '', + '🫶🏻' => '', + '🫶🏼' => '', + '🫶🏽' => '', + '🫶🏾' => '', + '🫶🏿' => '', + '👐🏻' => '', + '👐🏼' => '', + '👐🏽' => '', + '👐🏾' => '', + '👐🏿' => '', + '🤲🏻' => '', + '🤲🏼' => '', + '🤲🏽' => '', + '🤲🏾' => '', + '🤲🏿' => '', + '🤝🏻' => '', + '🤝🏼' => '', + '🤝🏽' => '', + '🤝🏾' => '', + '🤝🏿' => '', + '🙏🏻' => '', + '🙏🏼' => '', + '🙏🏽' => '', + '🙏🏾' => '', + '🙏🏿' => '', + '🫷🏻' => '', + '🫷🏼' => '', + '🫷🏽' => '', + '🫷🏾' => '', + '🫷🏿' => '', + '🫸🏻' => '', + '🫸🏼' => '', + '🫸🏽' => '', + '🫸🏾' => '', + '🫸🏿' => '', + '✍️' => '', + '✍🏻' => '', + '✍🏼' => '', + '✍🏽' => '', + '✍🏾' => '', + '✍🏿' => '', + '💅🏻' => '', + '💅🏼' => '', + '💅🏽' => '', + '💅🏾' => '', + '💅🏿' => '', + '🤳🏻' => '', + '🤳🏼' => '', + '🤳🏽' => '', + '🤳🏾' => '', + '🤳🏿' => '', + '💪🏻' => '', + '💪🏼' => '', + '💪🏽' => '', + '💪🏾' => '', + '💪🏿' => '', + '🦵🏻' => '', + '🦵🏼' => '', + '🦵🏽' => '', + '🦵🏾' => '', + '🦵🏿' => '', + '🦶🏻' => '', + '🦶🏼' => '', + '🦶🏽' => '', + '🦶🏾' => '', + '🦶🏿' => '', + '👂🏻' => '', + '👂🏼' => '', + '👂🏽' => '', + '👂🏾' => '', + '👂🏿' => '', + '🦻🏻' => '', + '🦻🏼' => '', + '🦻🏽' => '', + '🦻🏾' => '', + '🦻🏿' => '', + '👃🏻' => '', + '👃🏼' => '', + '👃🏽' => '', + '👃🏾' => '', + '👃🏿' => '', + '👁️' => '', + '👶🏻' => '', + '👶🏼' => '', + '👶🏽' => '', + '👶🏾' => '', + '👶🏿' => '', + '🧒🏻' => '', + '🧒🏼' => '', + '🧒🏽' => '', + '🧒🏾' => '', + '🧒🏿' => '', + '👦🏻' => '', + '👦🏼' => '', + '👦🏽' => '', + '👦🏾' => '', + '👦🏿' => '', + '👧🏻' => '', + '👧🏼' => '', + '👧🏽' => '', + '👧🏾' => '', + '👧🏿' => '', + '🧑🏻' => '', + '🧑🏼' => '', + '🧑🏽' => '', + '🧑🏾' => '', + '🧑🏿' => '', + '👱🏻' => '', + '👱🏼' => '', + '👱🏽' => '', + '👱🏾' => '', + '👱🏿' => '', + '👨🏻' => '', + '👨🏼' => '', + '👨🏽' => '', + '👨🏾' => '', + '👨🏿' => '', + '🧔🏻' => '', + '🧔🏼' => '', + '🧔🏽' => '', + '🧔🏾' => '', + '🧔🏿' => '', + '👩🏻' => '', + '👩🏼' => '', + '👩🏽' => '', + '👩🏾' => '', + '👩🏿' => '', + '🧓🏻' => '', + '🧓🏼' => '', + '🧓🏽' => '', + '🧓🏾' => '', + '🧓🏿' => '', + '👴🏻' => '', + '👴🏼' => '', + '👴🏽' => '', + '👴🏾' => '', + '👴🏿' => '', + '👵🏻' => '', + '👵🏼' => '', + '👵🏽' => '', + '👵🏾' => '', + '👵🏿' => '', + '🙍🏻' => '', + '🙍🏼' => '', + '🙍🏽' => '', + '🙍🏾' => '', + '🙍🏿' => '', + '🙎🏻' => '', + '🙎🏼' => '', + '🙎🏽' => '', + '🙎🏾' => '', + '🙎🏿' => '', + '🙅🏻' => '', + '🙅🏼' => '', + '🙅🏽' => '', + '🙅🏾' => '', + '🙅🏿' => '', + '🙆🏻' => '', + '🙆🏼' => '', + '🙆🏽' => '', + '🙆🏾' => '', + '🙆🏿' => '', + '💁🏻' => '', + '💁🏼' => '', + '💁🏽' => '', + '💁🏾' => '', + '💁🏿' => '', + '🙋🏻' => '', + '🙋🏼' => '', + '🙋🏽' => '', + '🙋🏾' => '', + '🙋🏿' => '', + '🧏🏻' => '', + '🧏🏼' => '', + '🧏🏽' => '', + '🧏🏾' => '', + '🧏🏿' => '', + '🙇🏻' => '', + '🙇🏼' => '', + '🙇🏽' => '', + '🙇🏾' => '', + '🙇🏿' => '', + '🤦🏻' => '', + '🤦🏼' => '', + '🤦🏽' => '', + '🤦🏾' => '', + '🤦🏿' => '', + '🤷🏻' => '', + '🤷🏼' => '', + '🤷🏽' => '', + '🤷🏾' => '', + '🤷🏿' => '', + '👮🏻' => '', + '👮🏼' => '', + '👮🏽' => '', + '👮🏾' => '', + '👮🏿' => '', + '🕵️' => '', + '🕵🏻' => '', + '🕵🏼' => '', + '🕵🏽' => '', + '🕵🏾' => '', + '🕵🏿' => '', + '💂🏻' => '', + '💂🏼' => '', + '💂🏽' => '', + '💂🏾' => '', + '💂🏿' => '', + '🥷🏻' => '', + '🥷🏼' => '', + '🥷🏽' => '', + '🥷🏾' => '', + '🥷🏿' => '', + '👷🏻' => '', + '👷🏼' => '', + '👷🏽' => '', + '👷🏾' => '', + '👷🏿' => '', + '🫅🏻' => '', + '🫅🏼' => '', + '🫅🏽' => '', + '🫅🏾' => '', + '🫅🏿' => '', + '🤴🏻' => '', + '🤴🏼' => '', + '🤴🏽' => '', + '🤴🏾' => '', + '🤴🏿' => '', + '👸🏻' => '', + '👸🏼' => '', + '👸🏽' => '', + '👸🏾' => '', + '👸🏿' => '', + '👳🏻' => '', + '👳🏼' => '', + '👳🏽' => '', + '👳🏾' => '', + '👳🏿' => '', + '👲🏻' => '', + '👲🏼' => '', + '👲🏽' => '', + '👲🏾' => '', + '👲🏿' => '', + '🧕🏻' => '', + '🧕🏼' => '', + '🧕🏽' => '', + '🧕🏾' => '', + '🧕🏿' => '', + '🤵🏻' => '', + '🤵🏼' => '', + '🤵🏽' => '', + '🤵🏾' => '', + '🤵🏿' => '', + '👰🏻' => '', + '👰🏼' => '', + '👰🏽' => '', + '👰🏾' => '', + '👰🏿' => '', + '🤰🏻' => '', + '🤰🏼' => '', + '🤰🏽' => '', + '🤰🏾' => '', + '🤰🏿' => '', + '🫃🏻' => '', + '🫃🏼' => '', + '🫃🏽' => '', + '🫃🏾' => '', + '🫃🏿' => '', + '🫄🏻' => '', + '🫄🏼' => '', + '🫄🏽' => '', + '🫄🏾' => '', + '🫄🏿' => '', + '🤱🏻' => '', + '🤱🏼' => '', + '🤱🏽' => '', + '🤱🏾' => '', + '🤱🏿' => '', + '👼🏻' => '', + '👼🏼' => '', + '👼🏽' => '', + '👼🏾' => '', + '👼🏿' => '', + '🎅🏻' => '', + '🎅🏼' => '', + '🎅🏽' => '', + '🎅🏾' => '', + '🎅🏿' => '', + '🤶🏻' => '', + '🤶🏼' => '', + '🤶🏽' => '', + '🤶🏾' => '', + '🤶🏿' => '', + '🦸🏻' => '', + '🦸🏼' => '', + '🦸🏽' => '', + '🦸🏾' => '', + '🦸🏿' => '', + '🦹🏻' => '', + '🦹🏼' => '', + '🦹🏽' => '', + '🦹🏾' => '', + '🦹🏿' => '', + '🧙🏻' => '', + '🧙🏼' => '', + '🧙🏽' => '', + '🧙🏾' => '', + '🧙🏿' => '', + '🧚🏻' => '', + '🧚🏼' => '', + '🧚🏽' => '', + '🧚🏾' => '', + '🧚🏿' => '', + '🧛🏻' => '', + '🧛🏼' => '', + '🧛🏽' => '', + '🧛🏾' => '', + '🧛🏿' => '', + '🧜🏻' => '', + '🧜🏼' => '', + '🧜🏽' => '', + '🧜🏾' => '', + '🧜🏿' => '', + '🧝🏻' => '', + '🧝🏼' => '', + '🧝🏽' => '', + '🧝🏾' => '', + '🧝🏿' => '', + '💆🏻' => '', + '💆🏼' => '', + '💆🏽' => '', + '💆🏾' => '', + '💆🏿' => '', + '💇🏻' => '', + '💇🏼' => '', + '💇🏽' => '', + '💇🏾' => '', + '💇🏿' => '', + '🚶🏻' => '', + '🚶🏼' => '', + '🚶🏽' => '', + '🚶🏾' => '', + '🚶🏿' => '', + '🧍🏻' => '', + '🧍🏼' => '', + '🧍🏽' => '', + '🧍🏾' => '', + '🧍🏿' => '', + '🧎🏻' => '', + '🧎🏼' => '', + '🧎🏽' => '', + '🧎🏾' => '', + '🧎🏿' => '', + '🏃🏻' => '', + '🏃🏼' => '', + '🏃🏽' => '', + '🏃🏾' => '', + '🏃🏿' => '', + '💃🏻' => '', + '💃🏼' => '', + '💃🏽' => '', + '💃🏾' => '', + '💃🏿' => '', + '🕺🏻' => '', + '🕺🏼' => '', + '🕺🏽' => '', + '🕺🏾' => '', + '🕺🏿' => '', + '🕴️' => '', + '🕴🏻' => '', + '🕴🏼' => '', + '🕴🏽' => '', + '🕴🏾' => '', + '🕴🏿' => '', + '🧖🏻' => '', + '🧖🏼' => '', + '🧖🏽' => '', + '🧖🏾' => '', + '🧖🏿' => '', + '🧗🏻' => '', + '🧗🏼' => '', + '🧗🏽' => '', + '🧗🏾' => '', + '🧗🏿' => '', + '🏇🏻' => '', + '🏇🏼' => '', + '🏇🏽' => '', + '🏇🏾' => '', + '🏇🏿' => '', + '⛷️' => '', + '🏂🏻' => '', + '🏂🏼' => '', + '🏂🏽' => '', + '🏂🏾' => '', + '🏂🏿' => '', + '🏌️' => '', + '🏌🏻' => '', + '🏌🏼' => '', + '🏌🏽' => '', + '🏌🏾' => '', + '🏌🏿' => '', + '🏄🏻' => '', + '🏄🏼' => '', + '🏄🏽' => '', + '🏄🏾' => '', + '🏄🏿' => '', + '🚣🏻' => '', + '🚣🏼' => '', + '🚣🏽' => '', + '🚣🏾' => '', + '🚣🏿' => '', + '🏊🏻' => '', + '🏊🏼' => '', + '🏊🏽' => '', + '🏊🏾' => '', + '🏊🏿' => '', + '⛹️' => '', + '⛹🏻' => '', + '⛹🏼' => '', + '⛹🏽' => '', + '⛹🏾' => '', + '⛹🏿' => '', + '🏋️' => '', + '🏋🏻' => '', + '🏋🏼' => '', + '🏋🏽' => '', + '🏋🏾' => '', + '🏋🏿' => '', + '🚴🏻' => '', + '🚴🏼' => '', + '🚴🏽' => '', + '🚴🏾' => '', + '🚴🏿' => '', + '🚵🏻' => '', + '🚵🏼' => '', + '🚵🏽' => '', + '🚵🏾' => '', + '🚵🏿' => '', + '🤸🏻' => '', + '🤸🏼' => '', + '🤸🏽' => '', + '🤸🏾' => '', + '🤸🏿' => '', + '🤽🏻' => '', + '🤽🏼' => '', + '🤽🏽' => '', + '🤽🏾' => '', + '🤽🏿' => '', + '🤾🏻' => '', + '🤾🏼' => '', + '🤾🏽' => '', + '🤾🏾' => '', + '🤾🏿' => '', + '🤹🏻' => '', + '🤹🏼' => '', + '🤹🏽' => '', + '🤹🏾' => '', + '🤹🏿' => '', + '🧘🏻' => '', + '🧘🏼' => '', + '🧘🏽' => '', + '🧘🏾' => '', + '🧘🏿' => '', + '🛀🏻' => '', + '🛀🏼' => '', + '🛀🏽' => '', + '🛀🏾' => '', + '🛀🏿' => '', + '🛌🏻' => '', + '🛌🏼' => '', + '🛌🏽' => '', + '🛌🏾' => '', + '🛌🏿' => '', + '👭🏻' => '', + '👭🏼' => '', + '👭🏽' => '', + '👭🏾' => '', + '👭🏿' => '', + '👫🏻' => '', + '👫🏼' => '', + '👫🏽' => '', + '👫🏾' => '', + '👫🏿' => '', + '👬🏻' => '', + '👬🏼' => '', + '👬🏽' => '', + '👬🏾' => '', + '👬🏿' => '', + '💏🏻' => '', + '💏🏼' => '', + '💏🏽' => '', + '💏🏾' => '', + '💏🏿' => '', + '💑🏻' => '', + '💑🏼' => '', + '💑🏽' => '', + '💑🏾' => '', + '💑🏿' => '', + '🗣️' => '', + '🐿️' => '', + '🕊️' => '', + '🕷️' => '', + '🕸️' => '', + '🏵️' => '', + '☘️' => '', + '🌶️' => '', + '🍽️' => '', + '🗺️' => '', + '🏔️' => '', + '⛰️' => '', + '🏕️' => '', + '🏖️' => '', + '🏜️' => '', + '🏝️' => '', + '🏞️' => '', + '🏟️' => '', + '🏛️' => '', + '🏗️' => '', + '🏘️' => '', + '🏚️' => '', + '⛩️' => '', + '🏙️' => '', + '♨️' => '', + '🏎️' => '', + '🏍️' => '', + '🛣️' => '', + '🛤️' => '', + '🛢️' => '', + '🛳️' => '', + '⛴️' => '', + '🛥️' => '', + '✈️' => '', + '🛩️' => '', + '🛰️' => '', + '🛎️' => '', + '⏱️' => '', + '⏲️' => '', + '🕰️' => '', + '🌡️' => '', + '☀️' => '', + '☁️' => '', + '⛈️' => '', + '🌤️' => '', + '🌥️' => '', + '🌦️' => '', + '🌧️' => '', + '🌨️' => '', + '🌩️' => '', + '🌪️' => '', + '🌫️' => '', + '🌬️' => '', + '☂️' => '', + '⛱️' => '', + '❄️' => '', + '☃️' => '', + '☄️' => '', + '🎗️' => '', + '🎟️' => '', + '🎖️' => '', + '⛸️' => '', + '🕹️' => '', + '♠️' => '', + '♥️' => '', + '♦️' => '', + '♣️' => '', + '♟️' => '', + '🖼️' => '', + '🕶️' => '', + '🛍️' => '', + '⛑️' => '', + '🎙️' => '', + '🎚️' => '', + '🎛️' => '', + '☎️' => '', + '🖥️' => '', + '🖨️' => '', + '⌨️' => '', + '🖱️' => '', + '🖲️' => '', + '🎞️' => '', + '📽️' => '', + '🕯️' => '', + '🗞️' => '', + '🏷️' => '', + '✉️' => '', + '🗳️' => '', + '✏️' => '', + '✒️' => '', + '🖋️' => '', + '🖊️' => '', + '🖌️' => '', + '🖍️' => '', + '🗂️' => '', + '🗒️' => '', + '🗓️' => '', + '🖇️' => '', + '✂️' => '', + '🗃️' => '', + '🗄️' => '', + '🗑️' => '', + '🗝️' => '', + '⛏️' => '', + '⚒️' => '', + '🛠️' => '', + '🗡️' => '', + '⚔️' => '', + '🛡️' => '', + '⚙️' => '', + '🗜️' => '', + '⚖️' => '', + '⛓️' => '', + '⚗️' => '', + '🛏️' => '', + '🛋️' => '', + '⚰️' => '', + '⚱️' => '', + '⚠️' => '', + '☢️' => '', + '☣️' => '', + '⬆️' => '', + '↗️' => '', + '➡️' => '', + '↘️' => '', + '⬇️' => '', + '↙️' => '', + '⬅️' => '', + '↖️' => '', + '↕️' => '', + '↔️' => '', + '↩️' => '', + '↪️' => '', + '⤴️' => '', + '⤵️' => '', + '⚛️' => '', + '🕉️' => '', + '✡️' => '', + '☸️' => '', + '☯️' => '', + '✝️' => '', + '☦️' => '', + '☪️' => '', + '☮️' => '', + '▶️' => '', + '⏭️' => '', + '⏯️' => '', + '◀️' => '', + '⏮️' => '', + '⏸️' => '', + '⏹️' => '', + '⏺️' => '', + '⏏️' => '', + '♀️' => '', + '♂️' => '', + '⚧️' => '', + '✖️' => '', + '♾️' => '', + '‼️' => '', + '⁉️' => '', + '〰️' => '', + '⚕️' => '', + '♻️' => '', + '⚜️' => '', + '☑️' => '', + '✔️' => '', + '〽️' => '', + '✳️' => '', + '✴️' => '', + '❇️' => '', + '©️' => '', + '®️' => '', + '™️' => '', + '#⃣' => '', + '*⃣' => '', + '0⃣' => '', + '1⃣' => '', + '2⃣' => '', + '3⃣' => '', + '4⃣' => '', + '5⃣' => '', + '6⃣' => '', + '7⃣' => '', + '8⃣' => '', + '9⃣' => '', + '🅰️' => '', + '🅱️' => '', + 'ℹ️' => '', + 'Ⓜ️' => '', + '🅾️' => '', + '🅿️' => '', + '🈂️' => '', + '🈷️' => '', + '㊗️' => '', + '㊙️' => '', + '◼️' => '', + '◻️' => '', + '▪️' => '', + '▫️' => '', + '🏳️' => '', + '🇦🇨' => '', + '🇦🇩' => '', + '🇦🇪' => '', + '🇦🇫' => '', + '🇦🇬' => '', + '🇦🇮' => '', + '🇦🇱' => '', + '🇦🇲' => '', + '🇦🇴' => '', + '🇦🇶' => '', + '🇦🇷' => '', + '🇦🇸' => '', + '🇦🇹' => '', + '🇦🇺' => '', + '🇦🇼' => '', + '🇦🇽' => '', + '🇦🇿' => '', + '🇧🇦' => '', + '🇧🇧' => '', + '🇧🇩' => '', + '🇧🇪' => '', + '🇧🇫' => '', + '🇧🇬' => '', + '🇧🇭' => '', + '🇧🇮' => '', + '🇧🇯' => '', + '🇧🇱' => '', + '🇧🇲' => '', + '🇧🇳' => '', + '🇧🇴' => '', + '🇧🇶' => '', + '🇧🇷' => '', + '🇧🇸' => '', + '🇧🇹' => '', + '🇧🇻' => '', + '🇧🇼' => '', + '🇧🇾' => '', + '🇧🇿' => '', + '🇨🇦' => '', + '🇨🇨' => '', + '🇨🇩' => '', + '🇨🇫' => '', + '🇨🇬' => '', + '🇨🇭' => '', + '🇨🇮' => '', + '🇨🇰' => '', + '🇨🇱' => '', + '🇨🇲' => '', + '🇨🇳' => '', + '🇨🇴' => '', + '🇨🇵' => '', + '🇨🇷' => '', + '🇨🇺' => '', + '🇨🇻' => '', + '🇨🇼' => '', + '🇨🇽' => '', + '🇨🇾' => '', + '🇨🇿' => '', + '🇩🇪' => '', + '🇩🇬' => '', + '🇩🇯' => '', + '🇩🇰' => '', + '🇩🇲' => '', + '🇩🇴' => '', + '🇩🇿' => '', + '🇪🇦' => '', + '🇪🇨' => '', + '🇪🇪' => '', + '🇪🇬' => '', + '🇪🇭' => '', + '🇪🇷' => '', + '🇪🇸' => '', + '🇪🇹' => '', + '🇪🇺' => '', + '🇫🇮' => '', + '🇫🇯' => '', + '🇫🇰' => '', + '🇫🇲' => '', + '🇫🇴' => '', + '🇫🇷' => '', + '🇬🇦' => '', + '🇬🇧' => '', + '🇬🇩' => '', + '🇬🇪' => '', + '🇬🇫' => '', + '🇬🇬' => '', + '🇬🇭' => '', + '🇬🇮' => '', + '🇬🇱' => '', + '🇬🇲' => '', + '🇬🇳' => '', + '🇬🇵' => '', + '🇬🇶' => '', + '🇬🇷' => '', + '🇬🇸' => '', + '🇬🇹' => '', + '🇬🇺' => '', + '🇬🇼' => '', + '🇬🇾' => '', + '🇭🇰' => '', + '🇭🇲' => '', + '🇭🇳' => '', + '🇭🇷' => '', + '🇭🇹' => '', + '🇭🇺' => '', + '🇮🇨' => '', + '🇮🇩' => '', + '🇮🇪' => '', + '🇮🇱' => '', + '🇮🇲' => '', + '🇮🇳' => '', + '🇮🇴' => '', + '🇮🇶' => '', + '🇮🇷' => '', + '🇮🇸' => '', + '🇮🇹' => '', + '🇯🇪' => '', + '🇯🇲' => '', + '🇯🇴' => '', + '🇯🇵' => '', + '🇰🇪' => '', + '🇰🇬' => '', + '🇰🇭' => '', + '🇰🇮' => '', + '🇰🇲' => '', + '🇰🇳' => '', + '🇰🇵' => '', + '🇰🇷' => '', + '🇰🇼' => '', + '🇰🇾' => '', + '🇰🇿' => '', + '🇱🇦' => '', + '🇱🇧' => '', + '🇱🇨' => '', + '🇱🇮' => '', + '🇱🇰' => '', + '🇱🇷' => '', + '🇱🇸' => '', + '🇱🇹' => '', + '🇱🇺' => '', + '🇱🇻' => '', + '🇱🇾' => '', + '🇲🇦' => '', + '🇲🇨' => '', + '🇲🇩' => '', + '🇲🇪' => '', + '🇲🇫' => '', + '🇲🇬' => '', + '🇲🇭' => '', + '🇲🇰' => '', + '🇲🇱' => '', + '🇲🇲' => '', + '🇲🇳' => '', + '🇲🇴' => '', + '🇲🇵' => '', + '🇲🇶' => '', + '🇲🇷' => '', + '🇲🇸' => '', + '🇲🇹' => '', + '🇲🇺' => '', + '🇲🇻' => '', + '🇲🇼' => '', + '🇲🇽' => '', + '🇲🇾' => '', + '🇲🇿' => '', + '🇳🇦' => '', + '🇳🇨' => '', + '🇳🇪' => '', + '🇳🇫' => '', + '🇳🇬' => '', + '🇳🇮' => '', + '🇳🇱' => '', + '🇳🇴' => '', + '🇳🇵' => '', + '🇳🇷' => '', + '🇳🇺' => '', + '🇳🇿' => '', + '🇴🇲' => '', + '🇵🇦' => '', + '🇵🇪' => '', + '🇵🇫' => '', + '🇵🇬' => '', + '🇵🇭' => '', + '🇵🇰' => '', + '🇵🇱' => '', + '🇵🇲' => '', + '🇵🇳' => '', + '🇵🇷' => '', + '🇵🇸' => '', + '🇵🇹' => '', + '🇵🇼' => '', + '🇵🇾' => '', + '🇶🇦' => '', + '🇷🇪' => '', + '🇷🇴' => '', + '🇷🇸' => '', + '🇷🇺' => '', + '🇷🇼' => '', + '🇸🇦' => '', + '🇸🇧' => '', + '🇸🇨' => '', + '🇸🇩' => '', + '🇸🇪' => '', + '🇸🇬' => '', + '🇸🇭' => '', + '🇸🇮' => '', + '🇸🇯' => '', + '🇸🇰' => '', + '🇸🇱' => '', + '🇸🇲' => '', + '🇸🇳' => '', + '🇸🇴' => '', + '🇸🇷' => '', + '🇸🇸' => '', + '🇸🇹' => '', + '🇸🇻' => '', + '🇸🇽' => '', + '🇸🇾' => '', + '🇸🇿' => '', + '🇹🇦' => '', + '🇹🇨' => '', + '🇹🇩' => '', + '🇹🇫' => '', + '🇹🇬' => '', + '🇹🇭' => '', + '🇹🇯' => '', + '🇹🇰' => '', + '🇹🇱' => '', + '🇹🇲' => '', + '🇹🇳' => '', + '🇹🇴' => '', + '🇹🇷' => '', + '🇹🇹' => '', + '🇹🇻' => '', + '🇹🇼' => '', + '🇹🇿' => '', + '🇺🇦' => '', + '🇺🇬' => '', + '🇺🇲' => '', + '🇺🇳' => '', + '🇺🇸' => '', + '🇺🇾' => '', + '🇺🇿' => '', + '🇻🇦' => '', + '🇻🇨' => '', + '🇻🇪' => '', + '🇻🇬' => '', + '🇻🇮' => '', + '🇻🇳' => '', + '🇻🇺' => '', + '🇼🇫' => '', + '🇼🇸' => '', + '🇽🇰' => '', + '🇾🇪' => '', + '🇾🇹' => '', + '🇿🇦' => '', + '🇿🇲' => '', + '🇿🇼' => '', + '😀' => '', + '😃' => '', + '😄' => '', + '😁' => '', + '😆' => '', + '😅' => '', + '🤣' => '', + '😂' => '', + '🙂' 10000 => '', + '🙃' => '', + '🫠' => '', + '😉' => '', + '😊' => '', + '😇' => '', + '🥰' => '', + '😍' => '', + '🤩' => '', + '😘' => '', + '😗' => '', + '☺' => '', + '😚' => '', + '😙' => '', + '🥲' => '', + '😋' => '', + '😛' => '', + '😜' => '', + '🤪' => '', + '😝' => '', + '🤑' => '', + '🤗' => '', + '🤭' => '', + '🫢' => '', + '🫣' => '', + '🤫' => '', + '🤔' => '', + '🫡' => '', + '🤐' => '', + '🤨' => '', + '😐' => '', + '😑' => '', + '😶' => '', + '🫥' => '', + '😏' => '', + '😒' => '', + '🙄' => '', + '😬' => '', + '🤥' => '', + '🫨' => '', + '😌' => '', + '😔' => '', + '😪' => '', + '🤤' => '', + '😴' => '', + '😷' => '', + '🤒' => '', + '🤕' => '', + '🤢' => '', + '🤮' => '', + '🤧' => '', + '🥵' => '', + '🥶' => '', + '🥴' => '', + '😵' => '', + '🤯' => '', + '🤠' => '', + '🥳' => '', + '🥸' => '', + '😎' => '', + '🤓' => '', + '🧐' => '', + '😕' => '', + '🫤' => '', + '😟' => '', + '🙁' => '', + '☹' => '', + '😮' => '', + '😯' => '', + '😲' => '', + '😳' => '', + '🥺' => '', + '🥹' => '', + '😦' => '', + '😧' => '', + '😨' => '', + '😰' => '', + '😥' => '', + '😢' => '', + '😭' => '', + '😱' => '', + '😖' => '', + '😣' => '', + '😞' => '', + '😓' => '', + '😩' => '', + '😫' => '', + '🥱' => '', + '😤' => '', + '😡' => '', + '😠' => '', + '🤬' => '', + '😈' => '', + '👿' => '', + '💀' => '', + '☠' => '', + '💩' => '', + '🤡' => '', + '👹' => '', + '👺' => '', + '👻' => '', + '👽' => '', + '👾' => '', + '🤖' => '', + '😺' => '', + '😸' => '', + '😹' => '', + '😻' => '', + '😼' => '', + '😽' => '', + '🙀' => '', + '😿' => '', + '😾' => '', + '🙈' => '', + '🙉' => '', + '🙊' => '', + '💋' => '', + '💌' => '', + '💘' => '', + '💝' => '', + '💖' => '', + '💗' => '', + '💓' => '', + '💞' => '', + '💕' => '', + '💟' => '', + '❣' => '', + '💔' => '', + '❤' => '', + '🧡' => '', + '💛' => '', + '💚' => '', + '💙' => '', + '💜' => '', + '🩵' => '', + '🩶' => '', + '🩷' => '', + '🤎' => '', + '🖤' => '', + '🤍' => '', + '💯' => '', + '💢' => '', + '💥' => '', + '💫' => '', + '💦' => '', + '💨' => '', + '🕳' => '', + '💣' => '', + '💬' => '', + '🗨' => '', + '🗯' => '', + '💭' => '', + '💤' => '', + '👋' => '', + '🤚' => '', + '🖐' => '', + '✋' => '', + '🖖' => '', + '🫱' => '', + '🫲' => '', + '🫳' => '', + '🫴' => '', + '👌' => '', + '🤌' => '', + '🤏' => '', + '✌' => '', + '🤞' => '', + '🫰' => '', + '🤟' => '', + '🤘' => '', + '🤙' => '', + '👈' => '', + '👉' => '', + '👆' => '', + '🖕' => '', + '👇' => '', + '☝' => '', + '🫵' => '', + '👍' => '', + '👎' => '', + '✊' => '', + '👊' => '', + '🤛' => '', + '🤜' => '', + '👏' => '', + '🙌' => '', + '🫶' => '', + '👐' => '', + '🤲' => '', + '🤝' => '', + '🙏' => '', + '🫷' => '', + '🫸' => '', + '✍' => '', + '💅' => '', + '🤳' => '', + '💪' => '', + '🦾' => '', + '🦿' => '', + '🦵' => '', + '🦶' => '', + '👂' => '', + '🦻' => '', + '👃' => '', + '🧠' => '', + '🫀' => '', + '🫁' => '', + '🦷' => '', + '🦴' => '', + '👀' => '', + '👁' => '', + '👅' => '', + '👄' => '', + '🫦' => '', + '👶' => '', + '🧒' => '', + '👦' => '', + '👧' => '', + '🧑' => '', + '👱' => '', + '👨' => '', + '🧔' => '', + '👩' => '', + '🧓' => '', + '👴' => '', + '👵' => '', + '🙍' => '', + '🙎' => '', + '🙅' => '', + '🙆' => '', + '💁' => '', + '🙋' => '', + '🧏' => '', + '🙇' => '', + '🤦' => '', + '🤷' => '', + '👮' => '', + '🕵' => '', + '💂' => '', + '🥷' => '', + '👷' => '', + '🫅' => '', + '🤴' => '', + '👸' => '', + '👳' => '', + '👲' => '', + '🧕' => '', + '🤵' => '', + '👰' => '', + '🤰' => '', + '🫃' => '', + '🫄' => '', + '🤱' => '', + '👼' => '', + '🎅' => '', + '🤶' => '', + '🦸' => '', + '🦹' => '', + '🧙' => '', + '🧚' => '', + '🧛' => '', + '🧜' => '', + '🧝' => '', + '🧞' => '', + '🧟' => '', + '🧌' => '', + '💆' => '', + '💇' => '', + '🚶' => '', + '🧍' => '', + '🧎' => '', + '🏃' => '', + '💃' => '', + '🕺' => '', + '🕴' => '', + '👯' => '', + '🧖' => '', + '🧗' => '', + '🤺' => '', + '🏇' => '', + '⛷' => '', + '🏂' => '', + '🏌' => '', + '🏄' => '', + '🚣' => '', + '🏊' => '', + '⛹' => '', + '🏋' => '', + '🚴' => '', + '🚵' => '', + '🤸' => '', + '🤼' => '', + '🤽' => '', + '🤾' => '', + '🤹' => '', + '🧘' => '', + '🛀' => '', + '🛌' => '', + '👭' => '', + '👫' => '', + '👬' => '', + '💏' => '', + '💑' => '', + '👪' => '', + '🗣' => '', + '👤' => '', + '👥' => '', + '🫂' => '', + '👣' => '', + '🏻' => '', + '🏼' => '', + '🏽' => '', + '🏾' => '', + '🏿' => '', + '🦰' => '', + '🦱' => '', + '🦳' => '', + '🦲' => '', + '🐵' => '', + '🐒' => '', + '🦍' => '', + '🦧' => '', + '🐶' => '', + '🐕' => '', + '🦮' => '', + '🐩' => '', + '🐺' => '', + '🦊' => '', + '🦝' => '', + '🐱' => '', + '🐈' => '', + '🦁' => '', + '🐯' => '', + '🐅' => '', + '🐆' => '', + '🐴' => '', + '🫎' => '', + '🫏' => '', + '🐎' => '', + '🦄' => '', + '🦓' => '', + '🦌' => '', + '🦬' => '', + '🐮' => '', + '🐂' => '', + '🐃' => '', + '🐄' => '', + '🐷' => '', + '🐖' => '', + '🐗' => '', + '🐽' => '', + '🐏' => '', + '🐑' => '', + '🐐' => '', + '🐪' => '', + '🐫' => '', + '🦙' => '', + '🦒' => '', + '🐘' => '', + '🦣' => '', + '🦏' => '', + '🦛' => '', + '🐭' => '', + '🐁' => '', + '🐀' => '', + '🐹' => '', + '🐰' => '', + '🐇' => '', + '🐿' => '', + '🦫' => '', + '🦔' => '', + '🦇' => '', + '🐻' => '', + '🐨' => '', + '🐼' => '', + '🦥' => '', + '🦦' => '', + '🦨' => '', + '🦘' => '', + '🦡' => '', + '🐾' => '', + '🦃' => '', + '🐔' => '', + '🐓' => '', + '🐣' => '', + '🐤' => '', + '🐥' => '', + '🐦' => '', + '🐧' => '', + '🕊' => '', + '🦅' => '', + '🦆' => '', + '🦢' => '', + '🦉' => '', + '🦤' => '', + '🪶' => '', + '🦩' => '', + '🦚' => '', + '🦜' => '', + '🪽' => '', + '🪿' => '', + '🐸' => '', + '🐊' => '', + '🐢' => '', + '🦎' => '', + '🐍' => '', + '🐲' => '', + '🐉' => '', + '🦕' => '', + '🦖' => '', + '🐳' => '', + '🐋' => '', + '🐬' => '', + '🦭' => '', + '🐟' => '', + '🐠' => '', + '🐡' => '', + '🦈' => '', + '🐙' => '', + '🐚' => '', + '🪸' => '', + '🪼' => '', + '🐌' => '', + '🦋' => '', + '🐛' => '', + '🐜' => '', + '🐝' => '', + '🪲' => '', + '🐞' => '', + '🦗' => '', + '🪳' => '', + '🕷' => '', + '🕸' => '', + '🦂' => '', + '🦟' => '', + '🪰' => '', + '🪱' => '', + '🦠' => '', + '💐' => '', + '🌸' => '', + '💮' => '', + '🪷' => '', + '🏵' => '', + '🌹' => '', + '🥀' => '', + '🌺' => '', + '🌻' => '', + '🌼' => '', + '🌷' => '', + '🪻' => '', + '🌱' => '', + '🪴' => '', + '🌲' => '', + '🌳' => '', + '🌴' => '', + '🌵' => '', + '🌾' => '', + '🌿' => '', + '☘' => '', + '🍀' => '', + '🍁' => '', + '🍂' => '', + '🍃' => '', + '🪹' => '', + '🪺' => '', + '🍇' => '', + '🍈' => '', + '🍉' => '', + '🍊' => '', + '🍋' => '', + '🍌' => '', + '🍍' => '', + '🥭' => '', + '🍎' => '', + '🍏' => '', + '🍐' => '', + '🍑' => '', + '🍒' => '', + '🍓' => '', + '🫐' => '', + '🥝' => '', + '🍅' => '', + '🫒' => '', + '🥥' => '', + '🥑' => '', + '🍆' => '', + '🥔' => '', + '🥕' => '', + '🌽' => '', + '🌶' => '', + '🫑' => '', + '🥒' => '', + '🥬' => '', + '🥦' => '', + '🧄' => '', + '🧅' => '', + '🍄' => '', + '🥜' => '', + '🫘' => '', + '🌰' => '', + '🫚' => '', + '🫛' => '', + '🍞' => '', + '🥐' => '', + '🥖' => '', + '🫓' => '', + '🥨' => '', + '🥯' => '', + '🥞' => '', + '🧇' => '', + '🧀' => '', + '🍖' => '', + '🍗' => '', + '🥩' => '', + '🥓' => '', + '🍔' => '', + '🍟' => '', + '🍕' => '', + '🌭' => '', + '🥪' => '', + '🌮' => '', + '🌯' => '', + '🫔' => '', + '🥙' => '', + '🧆' => '', + '🥚' => '', + '🍳' => '', + '🥘' => '', + '🍲' => '', + '🫕' => '', + '🥣' => '', + '🥗' => '', + '🍿' => '', + '🧈' => '', + '🧂' => '', + '🥫' => '', + '🍱' => '', + '🍘' => '', + '🍙' => '', + '🍚' => '', + '🍛' => '', + '🍜' => '', + '🍝' => '', + '🍠' => '', + '🍢' => '', + '🍣' => '', + '🍤' => '', + '🍥' => '', + '🥮' => '', + '🍡' => '', + '🥟' => '', + '🥠' => '', + '🥡' => '', + '🦀' => '', + '🦞' => '', + '🦐' => '', + '🦑' => '', + '🦪' => '', + '🍦' => '', + '🍧' => '', + '🍨' => '', + '🍩' => '', + '🍪' => '', + '🎂' => '', + '🍰' => '', + '🧁' => '', + '🥧' => '', + '🍫' => '', + '🍬' => '', + '🍭' => '', + '🍮' => '', + '🍯' => '', + '🍼' => '', + '🥛' => '', + '☕' => '', + '🫖' => '', + '🍵' => '', + '🍶' => '', + '🍾' => '', + '🍷' => '', + '🍸' => '', + '🍹' => '', + '🍺' => '', + '🍻' => '', + '🥂' => '', + '🥃' => '', + '🫗' => '', + '🥤' => '', + '🧋' => '', + '🧃' => '', + '🧉' => '', + '🧊' => '', + '🥢' => '', + '🍽' => '', + '🍴' => '', + '🥄' => '', + '🔪' => '', + '🫙' => '', + '🏺' => '', + '🌍' => '', + '🌎' => '', + '🌏' => '', + '🌐' => '', + '🗺' => '', + '🗾' => '', + '🧭' => '', + '🏔' => '', + '⛰' => '', + '🌋' => '', + '🗻' => '', + '🏕' => '', + '🏖' => '', + '🏜' => '', + '🏝' => '', + '🏞' => '', + '🏟' => '', + '🏛' => '', + '🏗' => '', + '🧱' => '', + '🪨' => '', + '🪵' => '', + '🛖' => '', + '🏘' => '', + '🏚' => '', + '🏠' => '', + '🏡' => '', + '🏢' => '', + '🏣' => '', + '🏤' => '', + '🏥' => '', + '🏦' => '', + '🏨' => '', + '🏩' => '', + '🏪' => '', + '🏫' => '', + '🏬' => '', + '🏭' => '', + '🏯' => '', + '🏰' => '', + '💒' => '', + '🗼' => '', + '🗽' => '', + '⛪' => '', + '🕌' => '', + '🛕' => '', + '🕍' => '', + '⛩' => '', + '🕋' => '', + '⛲' => '', + '⛺' => '', + '🌁' => '', + '🌃' => '', + '🏙' => '', + '🌄' => '', + '🌅' => '', + '🌆' => '', + '🌇' => '', + '🌉' => '', + '♨' => '', + '🎠' => '', + '🛝' => '', + '🎡' => '', + '🎢' => '', + '💈' => '', + '🎪' => '', + '🚂' => '', + '🚃' => '', + '🚄' => '', + '🚅' => '', + '🚆' => '', + '🚇' => '', + '🚈' => '', + '🚉' => '', + '🚊' => '', + '🚝' => '', + '🚞' => '', + '🚋' => '', + '🚌' => '', + '🚍' => '', + '🚎' => '', + '🚐' => '', + '🚑' => '', + '🚒' => '', + '🚓' => '', + '🚔' => '', + '🚕' => '', + '🚖' => '', + '🚗' => '', + '🚘' => '', + '🚙' => '', + '🛻' => '', + '🚚' => '', + '🚛' => '', + '🚜' => '', + '🏎' => '', + '🏍' => '', + '🛵' => '', + '🦽' => '', + '🦼' => '', + '🛺' => '', + '🚲' => '', + '🛴' => '', + '🛹' => '', + '🛼' => '', + '🚏' => '', + '🛣' => '', + '🛤' => '', + '🛢' => '', + '⛽' => '', + '🛞' => '', + '🚨' => '', + '🚥' => '', + '🚦' => '', + '🛑' => '', + '🚧' => '', + '⚓' => '', + '🛟' => '', + '⛵' => '', + '🛶' => '', + '🚤' => '', + '🛳' => '', + '⛴' => '', + '🛥' => '', + '🚢' => '', + '✈' => '', + '🛩' => '', + '🛫' => '', + '🛬' => '', + '🪂' => '', + '💺' => '', + '🚁' => '', + '🚟' => '', + '🚠' => '', + '🚡' => '', + '🛰' => '', + '🚀' => '', + '🛸' => '', + '🛎' => '', + '🧳' => '', + '⌛' => '', + '⏳' => '', + '⌚' => '', + '⏰' => '', + '⏱' => '', + '⏲' => '', + '🕰' => '', + '🕛' => '', + '🕧' => '', + '🕐' => '', + '🕜' => '', + '🕑' => '', + '🕝' => '', + '🕒' => '', + '🕞' => '', + '🕓' => '', + '🕟' => '', + '🕔' => '', + '🕠' => '', + '🕕' => '', + '🕡' => '', + '🕖' => '', + '🕢' => '', + '🕗' => '', + '🕣' => '', + '🕘' => '', + '🕤' => '', + '🕙' => '', + '🕥' => '', + '🕚' => '', + '🕦' => '', + '🌑' => '', + '🌒' => '', + '🌓' => '', + '🌔' => '', + '🌕' => '', + '🌖' => '', + '🌗' => '', + '🌘' => '', + '🌙' => '', + '🌚' => '', + '🌛' => '', + '🌜' => '', + '🌡' => '', + '☀' => '', + '🌝' => '', + '🌞' => '', + '🪐' => '', + '⭐' => '', + '🌟' => '', + '🌠' => '', + '🌌' => '', + '☁' => '', + '⛅' => '', + '⛈' => '', + '🌤' => '', + '🌥' => '', + '🌦' => '', + '🌧' => '', + '🌨' => '', + '🌩' => '', + '🌪' => '', + '🌫' => '', + '🌬' => '', + '🌀' => '', + '🌈' => '', + '🌂' => '', + '☂' => '', + '☔' => '', + '⛱' => '', + '⚡' => '', + '❄' => '', + '☃' => '', + '⛄' => '', + '☄' => '', + '🔥' => '', + '💧' => '', + '🌊' => '', + '🎃' => '', + '🎄' => '', + '🎆' => '', + '🎇' => '', + '🧨' => '', + '✨' => '', + '🎈' => '', + '🎉' => '', + '🎊' => '', + '🎋' => '', + '🎍' => '', + '🎎' => '', + '🎏' => '', + '🎐' => '', + '🎑' => '', + '🧧' => '', + '🎀' => '', + '🎁' => '', + '🎗' => '', + '🎟' => '', + '🎫' => '', + '🎖' => '', + '🏆' => '', + '🏅' => '', + '🥇' => '', + '🥈' => '', + '🥉' => '', + '⚽' => '', + '⚾' => '', + '🥎' => '', + '🏀' => '', + '🏐' => '', + '🏈' => '', + '🏉' => '', + '🎾' => '', + '🥏' => '', + '🎳' => '', + '🏏' => '', + '🏑' => '', + '🏒' => '', + '🥍' => '', + '🏓' => '', + '🏸' => '', + '🥊' => '', + '🥋' => '', + '🥅' => '', + '⛳' => '', + '⛸' => '', + '🎣' => '', + '🤿' => '', + '🎽' => '', + '🎿' => '', + '🛷' => '', + '🥌' => '', + '🎯' => '', + '🪀' => '', + '🪁' => '', + '🎱' => '', + '🔮' => '', + '🪄' => '', + '🧿' => '', + '🪬' => '', + '🎮' => '', + '🕹' => '', + '🎰' => '', + '🎲' => '', + '🧩' => '', + '🧸' => '', + '🪅' => '', + '🪩' => '', + '🪆' => '', + '♠' => '', + '♥' => '', + '♦' => '', + '♣' => '', + '♟' => '', + '🃏' => '', + '🀄' => '', + '🎴' => '', + '🎭' => '', + '🖼' => '', + '🎨' => '', + '🧵' => '', + '🪡' => '', + '🧶' => '', + '🪢' => '', + '👓' => '', + '🕶' => '', + '🥽' => '', + '🥼' => '', + '🦺' => '', + '👔' => '', + '👕' => '', + '👖' => '', + '🧣' => '', + '🧤' => '', + '🧥' => '', + '🧦' => '', + '👗' => '', + '👘' => '', + '🥻' => '', + '🩱' => '', + '🩲' => '', + '🩳' => '', + '👙' => '', + '👚' => '', + '🪭' => '', + '🪮' => '', + '👛' => '', + '👜' => '', + '👝' => '', + '🛍' => '', + '🎒' => '', + '🩴' => '', + '👞' => '', + '👟' => '', + '🥾' => '', + '🥿' => '', + '👠' => '', + '👡' => '', + '🩰' => '', + '👢' => '', + '👑' => '', + '👒' => '', + '🎩' => '', + '🎓' => '', + '🧢' => '', + '🪖' => '', + '⛑' => '', + '📿' => '', + '💄' => '', + '💍' => '', + '💎' => '', + '🔇' => '', + '🔈' => '', + '🔉' => '', + '🔊' => '', + '📢' => '', + '📣' => '', + '📯' => '', + '🔔' => '', + '🔕' => '', + '🎼' => '', + '🎵' => '', + '🎶' => '', + '🎙' => '', + '🎚' => '', + '🎛' => '', + '🎤' => '', + '🎧' => '', + '📻' => '', + '🎷' => '', + '🪗' => '', + '🎸' => '', + '🎹' => '', + '🎺' => '', + '🎻' => '', + '🪕' => '', + '🥁' => '', + '🪘' => '', + '🪇' => '', + '🪈' => '', + '📱' => '', + '📲' => '', + '☎' => '', + '📞' => '', + '📟' => '', + '📠' => '', + '🔋' => '', + '🪫' => '', + '🔌' => '', + '💻' => '', + '🖥' => '', + '🖨' => '', + '⌨' => '', + '🖱' => '', + '🖲' => '', + '💽' => '', + '💾' => '', + '💿' => '', + '📀' => '', + '🧮' => '', + '🎥' => '', + '🎞' => '', + '📽' => '', + '🎬' => '', + '📺' => '', + '📷' => '', + '📸' => '', + '📹' => '', + '📼' => '', + '🔍' => '', + '🔎' => '', + '🕯' => '', + '💡' => '', + '🔦' => '', + '🏮' => '', + '🪔' => '', + '📔' => '', + '📕' => '', + '📖' => '', + '📗' => '', + '📘' => '', + '📙' => '', + '📚' => '', + '📓' => '', + '📒' => '', + '📃' => '', + '📜' => '', + '📄' => '', + '📰' => '', + '🗞' => '', + '📑' => '', + '🔖' => '', + '🏷' => '', + '💰' => '', + '🪙' => '', + '💴' => '', + '💵' => '', + '💶' => '', + '💷' => '', + '💸' => '', + '💳' => '', + '🧾' => '', + '💹' => '', + '✉' => '', + '📧' => '', + '📨' => '', + '📩' => '', + '📤' => '', + '📥' => '', + '📦' => '', + '📫' => '', + '📪' => '', + '📬' => '', + '📭' => '', + '📮' => '', + '🗳' => '', + '✏' => '', + '✒' => '', + '🖋' => '', + '🖊' => '', + '🖌' => '', + '🖍' => '', + '📝' => '', + '💼' => '', + '📁' => '', + '📂' => '', + '🗂' => '', + '📅' => '', + '📆' => '', + '🗒' => '', + '🗓' => '', + '📇' => '', + '📈' => '', + '📉' => '', + '📊' => '', + '📋' => '', + '📌' => '', + '📍' => '', + '📎' => '', + '🖇' => '', + '📏' => '', + '📐' => '', + '✂' => '', + '🗃' => '', + '🗄' => '', + '🗑' => '', + '🔒' => '', + '🔓' => '', + '🔏' => '', + '🔐' => '', + '🔑' => '', + '🗝' => '', + '🔨' => '', + '🪓' => '', + '⛏' => '', + '⚒' => '', + '🛠' => '', + '🗡' => '', + '⚔' => '', + '🔫' => '', + '🪃' => '', + '🏹' => '', + '🛡' => '', + '🪚' => '', + '🔧' => '', + '🪛' => '', + '🔩' => '', + '⚙' => '', + '🗜' => '', + '⚖' => '', + '🦯' => '', + '🔗' => '', + '⛓' => '', + '🪝' => '', + '🧰' => '', + '🧲' => '', + '🪜' => '', + '⚗' => '', + '🧪' => '', + '🧫' => '', + '🧬' => '', + '🔬' => '', + '🔭' => '', + '📡' => '', + '💉' => '', + '🩸' => '', + '💊' => '', + '🩹' => '', + '🩼' => '', + '🩺' => '', + '🩻' => '', + '🚪' => '', + '🛗' => '', + '🪞' => '', + '🪟' => '', + '🛏' => '', + '🛋' => '', + '🪑' => '', + '🚽' => '', + '🪠' => '', + '🚿' => '', + '🛁' => '', + '🪤' => '', + '🪒' => '', + '🧴' => '', + '🧷' => '', + '🧹' => '', + '🧺' => '', + '🧻' => '', + '🪣' => '', + '🧼' => '', + '🫧' => '', + '🪥' => '', + '🧽' => '', + '🧯' => '', + '🛒' => '', + '🚬' => '', + '⚰' => '', + '🪦' => '', + '⚱' => '', + '🗿' => '', + '🪧' => '', + '🪪' => '', + '🏧' => '', + '🚮' => '', + '🚰' => '', + '♿' => '', + '🚹' => '', + '🚺' => '', + '🚻' => '', + '🚼' => '', + '🚾' => '', + '🛂' => '', + '🛃' => '', + '🛄' => '', + '🛅' => '', + '⚠' => '', + '🚸' => '', + '⛔' => '', + '🚫' => '', + '🚳' => '', + '🚭' => '', + '🚯' => '', + '🚱' => '', + '🚷' => '', + '📵' => '', + '🔞' => '', + '☢' => '', + '☣' => '', + '⬆' => '', + '↗' => '', + '➡' => '', + '↘' => '', + '⬇' => '', + '↙' => '', + '⬅' => '', + '↖' => '', + '↕' => '', + '↔' => '', + '↩' => '', + '↪' => '', + '⤴' => '', + '⤵' => '', + '🔃' => '', + '🔄' => '', + '🔙' => '', + '🔚' => '', + '🔛' => '', + '🔜' => '', + '🔝' => '', + '🛐' => '', + '⚛' => '', + '🕉' => '', + '✡' => '', + '☸' => '', + '☯' => '', + '✝' => '', + '☦' => '', + '☪' => '', + '☮' => '', + '🕎' => '', + '🔯' => '', + '🪯' => '', + '♈' => '', + '♉' => '', + '♊' => '', + '♋' => '', + '♌' => '', + '♍' => '', + '♎' => '', + '♏' => '', + '♐' => '', + '♑' => '', + '♒' => '', + '♓' => '', + '⛎' => '', + '🔀' => '', + '🔁' => '', + '🔂' => '', + '▶' => '', + '⏩' => '', + '⏭' => '', + '⏯' => '', + '◀' => '', + '⏪' => '', + '⏮' => '', + '🔼' => '', + '⏫' => '', + '🔽' => '', + '⏬' => '', + '⏸' => '', + '⏹' => '', + '⏺' => '', + '⏏' => '', + '🎦' => '', + '🔅' => '', + '🔆' => '', + '📶' => '', + '📳' => '', + '📴' => '', + '🛜' => '', + '♀' => '', + '♂' => '', + '⚧' => '', + '✖' => '', + '➕' => '', + '➖' => '', + '➗' => '', + '🟰' => '', + '♾' => '', + '‼' => '', + '⁉' => '', + '❓' => '', + '❔' => '', + '❕' => '', + '❗' => '', + '〰' => '', + '💱' => '', + '💲' => '', + '⚕' => '', + '♻' => '', + '⚜' => '', + '🔱' => '', + '📛' => '', + '🔰' => '', + '⭕' => '', + '✅' => '', + '☑' => '', + '✔' => '', + '❌' => '', + '❎' => '', + '➰' => '', + '➿' => '', + '〽' => '', + '✳' => '', + '✴' => '', + '❇' => '', + '©' => '', + '®' => '', + '™' => '', + '🔟' => '', + '🔠' => '', + '🔡' => '', + '🔢' => '', + '🔣' => '', + '🔤' => '', + '🅰' => '', + '🆎' => '', + '🅱' => '', + '🆑' => '', + '🆒' => '', + '🆓' => '', + 'ℹ' => '', + '🆔' => '', + 'Ⓜ' => '', + '🆕' => '', + '🆖' => '', + '🅾' => '', + '🆗' => '', + '🅿' => '', + '🆘' => '', + '🆙' => '', + '🆚' => '', + '🈁' => '', + '🈂' => '', + '🈷' => '', + '🈶' => '', + '🈯' => '', + '🉐' => '', + '🈹' => '', + '🈚' => '', + '🈲' => '', + '🉑' => '', + '🈸' => '', + '🈴' => '', + '🈳' => '', + '㊗' => '', + '㊙' => '', + '🈺' => '', + '🈵' => '', + '🔴' => '', + '🟠' => '', + '🟡' => '', + '🟢' => '', + '🔵' => '', + '🟣' => '', + '🟤' => '', + '⚫' => '', + '⚪' => '', + '🟥' => '', + '🟧' => '', + '🟨' => '', + '🟩' => '', + '🟦' => '', + '🟪' => '', + '🟫' => '', + '⬛' => '', + '⬜' => '', + '◼' => '', + '◻' => '', + '◾' => '', + '◽' => '', + '▪' => '', + '▫' => '', + '🔶' => '', + '🔷' => '', + '🔸' => '', + '🔹' => '', + '🔺' => '', + '🔻' => '', + '💠' => '', + '🔘' => '', + '🔳' => '', + '🔲' => '', + '🏁' => '', + '🚩' => '', + '🎌' => '', + '🏴' => '', + '🏳' => '', +]; diff --git a/src/Symfony/Component/Intl/Resources/emoji/build.php b/src/Symfony/Component/Intl/Resources/emoji/build.php index cf34ea11baf2c..54e7fe10fde96 100755 --- a/src/Symfony/Component/Intl/Resources/emoji/build.php +++ b/src/Symfony/Component/Intl/Resources/emoji/build.php @@ -21,6 +21,7 @@ Builder::saveRules(Builder::buildRules($emojisCodePoints)); Builder::saveRules(Builder::buildGitHubRules($emojisCodePoints)); Builder::saveRules(Builder::buildSlackRules($emojisCodePoints)); +Builder::saveRules(Builder::buildStripRules($emojisCodePoints)); final class Builder { @@ -159,6 +160,18 @@ public static function buildSlackRules(array $emojisCodePoints): iterable return ['slack' => self::createRules($maps)]; } + public static function buildStripRules(array $emojisCodePoints): iterable + { + $maps = []; + foreach ($emojisCodePoints as $emoji) { + self::testEmoji($emoji, 'strip'); + $codePointsCount = mb_strlen($emoji); + $maps[$codePointsCount][$emoji] = ''; + } + + return ['strip' => self::createRules($maps)]; + } + public static function cleanTarget(): void { $fs = new Filesystem(); @@ -181,7 +194,7 @@ public static function saveRules(iterable $rulesByLocale): void $firstChars[$c] = $c; } - if (':' === $v[0]) { + if (':' === ($v[0] ?? null)) { file_put_contents(self::TARGET_DIR."/$locale-emoji.php", "in(__DIR__.'/../../Resources/data/transliterator/emoji') ->name('*.php') + ->notName('emoji-strip.php') ->files() ; From 81c5c0c6683feb14663f3d26dc987346befeca71 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 29 Nov 2022 09:12:04 +0100 Subject: [PATCH 017/475] [Validator] Add `Uuid::TIME_BASED_VERSIONS` to match that a UUID being validated embeds a timestamp --- src/Symfony/Component/Validator/CHANGELOG.md | 5 +++ .../Component/Validator/Constraints/Uuid.php | 7 ++++ .../Validator/Constraints/UuidValidator.php | 4 ++- .../Tests/Constraints/UuidValidatorTest.php | 33 +++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 5d55f9cde0b31..88b152ed8dc31 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add `Uuid::TIME_BASED_VERSIONS` to match that a UUID being validated embeds a timestamp + 6.2 --- diff --git a/src/Symfony/Component/Validator/Constraints/Uuid.php b/src/Symfony/Component/Validator/Constraints/Uuid.php index 4c9dedd8fe94a..4c385d5322ca6 100644 --- a/src/Symfony/Component/Validator/Constraints/Uuid.php +++ b/src/Symfony/Component/Validator/Constraints/Uuid.php @@ -28,6 +28,7 @@ class Uuid extends Constraint public const INVALID_CHARACTERS_ERROR = '51120b12-a2bc-41bf-aa53-cd73daf330d0'; public const INVALID_HYPHEN_PLACEMENT_ERROR = '98469c83-0309-4f5d-bf95-a496dcaa869c'; public const INVALID_VERSION_ERROR = '21ba13b4-b185-4882-ac6f-d147355987eb'; + public const INVALID_TIME_BASED_VERSION_ERROR = '484081ca-6fbd-11ed-ade8-a3bdfd0fcf2f'; public const INVALID_VARIANT_ERROR = '164ef693-2b9d-46de-ad7f-836201f0c2db'; protected const ERROR_NAMES = [ @@ -65,6 +66,12 @@ class Uuid extends Constraint self::V8_CUSTOM, ]; + public const TIME_BASED_VERSIONS = [ + self::V1_MAC, + self::V6_SORTABLE, + self::V7_MONOTONIC, + ]; + /** * Message to display when validation fails. * diff --git a/src/Symfony/Component/Validator/Constraints/UuidValidator.php b/src/Symfony/Component/Validator/Constraints/UuidValidator.php index c4ccfb27dee9d..88f9e9674ad11 100644 --- a/src/Symfony/Component/Validator/Constraints/UuidValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UuidValidator.php @@ -235,9 +235,11 @@ private function validateStrict(string $value, Uuid $constraint) // Check version if (!\in_array($value[self::STRICT_VERSION_POSITION], $constraint->versions)) { + $code = Uuid::TIME_BASED_VERSIONS === $constraint->versions ? Uuid::INVALID_TIME_BASED_VERSION_ERROR : Uuid::INVALID_VERSION_ERROR; + $this->context->buildViolation($constraint->message) ->setParameter('{{ value }}', $this->formatValue($value)) - ->setCode(Uuid::INVALID_VERSION_ERROR) + ->setCode($code) ->addViolation(); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php index 2df1b276206cc..f003afa6e2461 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php @@ -263,4 +263,37 @@ public function testInvalidNonStrictUuidNamed() ->setCode(Uuid::INVALID_CHARACTERS_ERROR) ->assertRaised(); } + + /** + * @dataProvider getUuidForTimeBasedAssertions + */ + public function testTimeBasedUuid(string $uid, bool $expectedTimeBased) + { + $constraint = new Uuid([ + 'versions' => Uuid::TIME_BASED_VERSIONS, + ]); + + $this->validator->validate($uid, $constraint); + + if ($expectedTimeBased) { + $this->assertNoViolation(); + } else { + $this->buildViolation('This is not a valid UUID.') + ->setParameter('{{ value }}', '"'.$uid.'"') + ->setCode(Uuid::INVALID_TIME_BASED_VERSION_ERROR) + ->assertRaised(); + } + } + + public static function getUuidForTimeBasedAssertions(): \Generator + { + yield Uuid::V1_MAC => ['95ab107e-6fc2-11ed-9d6b-01bfd6e71dbd', true]; + yield Uuid::V2_DCE => ['216fff40-98d9-21e3-a5e2-0800200c9a66', false]; + yield Uuid::V3_MD5 => ['5d5b5ae1-5857-3531-9431-e8ac73c3e61d', false]; + yield Uuid::V4_RANDOM => ['ba6479dd-a5ea-403c-96ae-5964d0582e81', false]; + yield Uuid::V5_SHA1 => ['fc1cc19d-cb3c-5f6a-a0f6-706424f68e3a', false]; + yield Uuid::V6_SORTABLE => ['1ed6fc29-5ab1-6b6e-8aad-cfa821180d14', true]; + yield Uuid::V7_MONOTONIC => ['0184c292-b133-7e10-a3b4-d49c1ab49b2a', true]; + yield Uuid::V8_CUSTOM => ['00112233-4455-8677-8899-aabbccddeeff', false]; + } } From 44c7c58f585865c6c1e1b9896e0cd88fef11659d Mon Sep 17 00:00:00 2001 From: tigitz Date: Fri, 30 Sep 2022 23:29:24 +0200 Subject: [PATCH 018/475] Leverage class name literal on object --- .../DoctrineExtensionTest.php | 8 ++++---- .../Console/Descriptor/JsonDescriptor.php | 2 +- .../Console/Descriptor/MarkdownDescriptor.php | 2 +- .../Console/Descriptor/TextDescriptor.php | 2 +- .../Console/Descriptor/XmlDescriptor.php | 2 +- .../Command/DebugFirewallCommand.php | 2 +- .../DataCollector/SecurityDataCollector.php | 2 +- .../Console/Command/CompleteCommand.php | 4 ++-- .../Component/Console/Tests/ApplicationTest.php | 4 ++-- .../Dumper/GraphvizDumper.php | 2 +- .../DependencyInjection/ServiceLocator.php | 2 +- .../Compiler/ResolveChildDefinitionsPassTest.php | 8 ++++---- .../Tests/Compiler/ServiceLocatorTagPassTest.php | 16 ++++++++-------- .../ErrorHandler/Tests/ErrorHandlerTest.php | 4 ++-- .../Tests/Exception/FlattenExceptionTest.php | 8 ++++---- .../EventDispatcher/Debug/WrappedListener.php | 2 +- .../Component/Form/Command/DebugCommand.php | 2 +- .../Form/Console/Descriptor/Descriptor.php | 2 +- .../Form/Console/Descriptor/JsonDescriptor.php | 2 +- .../Form/Console/Descriptor/TextDescriptor.php | 4 ++-- .../Form/Extension/Core/Type/FormType.php | 2 +- .../Csrf/Type/FormTypeCsrfExtension.php | 4 ++-- .../DataCollector/FormDataCollector.php | 2 +- .../DataCollector/FormDataExtractor.php | 2 +- .../Form/Tests/Fixtures/TestExtension.php | 2 +- .../Form/Tests/FormFactoryBuilderTest.php | 2 +- .../Component/HttpClient/RetryableHttpClient.php | 4 ++-- .../HttpClient/Tests/HttplugClientTest.php | 2 +- .../HttpKernel/Controller/ArgumentResolver.php | 2 +- .../ArgumentResolver/TraceableValueResolver.php | 4 ++-- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- .../Component/HttpKernel/Profiler/Profiler.php | 2 +- .../Component/HttpKernel/Tests/KernelTest.php | 4 ++-- .../Security/CheckLdapCredentialsListener.php | 2 +- .../Lock/Store/DoctrineDbalPostgreSqlStore.php | 2 +- .../Bridge/Amqp/Tests/Fixtures/long_receiver.php | 2 +- .../Command/AbstractFailedMessagesCommand.php | 2 +- .../Command/FailedMessagesShowCommand.php | 4 ++-- ...ndFailedMessageToFailureTransportListener.php | 2 +- .../Exception/HandlerFailedException.php | 2 +- .../Exception/ValidationFailedException.php | 2 +- .../Messenger/Handler/HandlersLocator.php | 2 +- .../Middleware/SendMessageMiddleware.php | 2 +- .../Transport/Serialization/Serializer.php | 2 +- .../SendFailedMessageToNotifierListener.php | 2 +- .../Http/Authentication/AuthenticatorManager.php | 12 ++++++------ .../Debug/TraceableAuthenticator.php | 2 +- .../Http/EventListener/RememberMeListener.php | 2 +- .../Serializer/Debug/TraceableEncoder.php | 4 ++-- .../Serializer/Debug/TraceableNormalizer.php | 4 ++-- .../Tests/Normalizer/CustomNormalizerTest.php | 4 ++-- .../Tests/Normalizer/ObjectNormalizerTest.php | 4 ++-- .../Component/Validator/ConstraintViolation.php | 2 +- .../Validator/Constraints/CallbackValidator.php | 2 +- .../DataCollector/ValidatorDataCollector.php | 2 +- .../Tests/Constraints/FileValidatorTest.php | 2 +- .../Tests/Fixtures/FakeMetadataFactory.php | 4 ++-- .../Tests/Caster/ExceptionCasterTest.php | 4 ++-- .../VarDumper/Tests/Caster/StubCasterTest.php | 4 ++-- .../EventListener/AuditTrailListener.php | 6 +++--- 60 files changed, 100 insertions(+), 100 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php index b481ab9569c2c..8880455cd9077 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -77,7 +77,7 @@ public function testFixManagersAutoMappingsWithTwoAutomappings() 'SecondBundle' => 'My\SecondBundle', ]; - $reflection = new \ReflectionClass(\get_class($this->extension)); + $reflection = new \ReflectionClass($this->extension); $method = $reflection->getMethod('fixManagersAutoMappings'); $method->invoke($this->extension, $emConfigs, $bundles); @@ -165,7 +165,7 @@ public function testFixManagersAutoMappings(array $originalEm1, array $originalE 'SecondBundle' => 'My\SecondBundle', ]; - $reflection = new \ReflectionClass(\get_class($this->extension)); + $reflection = new \ReflectionClass($this->extension); $method = $reflection->getMethod('fixManagersAutoMappings'); $newEmConfigs = $method->invoke($this->extension, $emConfigs, $bundles); @@ -182,7 +182,7 @@ public function testMappingTypeDetection() { $container = $this->createContainer(); - $reflection = new \ReflectionClass(\get_class($this->extension)); + $reflection = new \ReflectionClass($this->extension); $method = $reflection->getMethod('detectMappingType'); // The ordinary fixtures contain annotation @@ -326,7 +326,7 @@ public function testBundleAutoMapping(string $bundle, string $expectedType, stri $container = $this->createContainer([], [$bundle => $bundleClassName]); - $reflection = new \ReflectionClass(\get_class($this->extension)); + $reflection = new \ReflectionClass($this->extension); $method = $reflection->getMethod('getMappingDriverBundleConfigDefaults'); $this->assertSame( diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index b47b37c7398fc..00828ed32fc44 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -328,7 +328,7 @@ private function getCallableData(mixed $callable): array if (\is_object($callable[0])) { $data['name'] = $callable[1]; - $data['class'] = \get_class($callable[0]); + $data['class'] = $callable[0]::class; } else { if (!str_starts_with($callable[1], 'parent::')) { $data['name'] = $callable[1]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 555911a761506..9a3c5791b519f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -345,7 +345,7 @@ protected function describeCallable(mixed $callable, array $options = []) if (\is_object($callable[0])) { $string .= "\n".sprintf('- Name: `%s`', $callable[1]); - $string .= "\n".sprintf('- Class: `%s`', \get_class($callable[0])); + $string .= "\n".sprintf('- Class: `%s`', $callable[0]::class); } else { if (!str_starts_with($callable[1], 'parent::')) { $string .= "\n".sprintf('- Name: `%s`', $callable[1]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 1fb75fbbf6c35..366523c304d48 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -602,7 +602,7 @@ private function formatCallable(mixed $callable): string { if (\is_array($callable)) { if (\is_object($callable[0])) { - return sprintf('%s::%s()', \get_class($callable[0]), $callable[1]); + return sprintf('%s::%s()', $callable[0]::class, $callable[1]); } return sprintf('%s::%s()', $callable[0], $callable[1]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index 955a278d86ebb..241ac9ff4debd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -523,7 +523,7 @@ private function getCallableDocument(mixed $callable): \DOMDocument if (\is_object($callable[0])) { $callableXML->setAttribute('name', $callable[1]); - $callableXML->setAttribute('class', \get_class($callable[0])); + $callableXML->setAttribute('class', $callable[0]::class); } else { if (!str_starts_with($callable[1], 'parent::')) { $callableXML->setAttribute('name', $callable[1]); diff --git a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php index 757f61629f6f5..e8f58a74b08e6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php @@ -230,7 +230,7 @@ private function formatCallable(mixed $callable): string { if (\is_array($callable)) { if (\is_object($callable[0])) { - return sprintf('%s::%s()', \get_class($callable[0]), $callable[1]); + return sprintf('%s::%s()', $callable[0]::class, $callable[1]); } return sprintf('%s::%s()', $callable[0], $callable[1]); diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index 96730d041c5fc..6f0e496477702 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -145,7 +145,7 @@ public function collect(Request $request, Response $response, \Throwable $except foreach ($decisionLog as $key => $log) { $decisionLog[$key]['voter_details'] = []; foreach ($log['voterDetails'] as $voterDetail) { - $voterClass = \get_class($voterDetail['voter']); + $voterClass = $voterDetail['voter']::class; $classData = $this->hasVarDumper ? new ClassStub($voterClass) : $voterClass; $decisionLog[$key]['voter_details'][] = [ 'class' => $classData, diff --git a/src/Symfony/Component/Console/Command/CompleteCommand.php b/src/Symfony/Component/Console/Command/CompleteCommand.php index e65b334ce89db..ba3042e786278 100644 --- a/src/Symfony/Component/Console/Command/CompleteCommand.php +++ b/src/Symfony/Component/Console/Command/CompleteCommand.php @@ -134,12 +134,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $completionInput->bind($command->getDefinition()); if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) { - $this->log(' Completing option names for the '.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).' command.'); + $this->log(' Completing option names for the '.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.' command.'); $suggestions->suggestOptions($command->getDefinition()->getOptions()); } else { $this->log([ - ' Completing using the '.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).' class.', + ' Completing using the '.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.' class.', ' Completing '.$completionInput->getCompletionType().' for '.$completionInput->getCompletionName().'', ]); if (null !== $compval = $completionInput->getCompletionValue()) { diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index c178151688928..03c60dbccd96a 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -922,7 +922,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.', (new class() { })::class)); }); $tester = new ApplicationTester($application); @@ -948,7 +948,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.', (new class() { })::class)); }); $tester = new ApplicationTester($application); diff --git a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php index 54b60631fc7c4..11342815fcdea 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php @@ -174,7 +174,7 @@ private function findNodes(): array } if (!$container->hasDefinition($id)) { - $nodes[$id] = ['class' => str_replace('\\', '\\\\', \get_class($container->get($id))), 'attributes' => $this->options['node.instance']]; + $nodes[$id] = ['class' => str_replace('\\', '\\\\', $container->get($id)::class), 'attributes' => $this->options['node.instance']]; } } diff --git a/src/Symfony/Component/DependencyInjection/ServiceLocator.php b/src/Symfony/Component/DependencyInjection/ServiceLocator.php index 94dbfd0ad9018..3560ce3ccdd0e 100644 --- a/src/Symfony/Component/DependencyInjection/ServiceLocator.php +++ b/src/Symfony/Component/DependencyInjection/ServiceLocator.php @@ -87,7 +87,7 @@ private function createNotFoundException(string $id): NotFoundExceptionInterface } $class = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 4); - $class = isset($class[3]['object']) ? \get_class($class[3]['object']) : null; + $class = isset($class[3]['object']) ? $class[3]['object']::class : null; $externalId = $this->externalId ?: $class; $msg = []; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php index 0c137a39a03ba..f1ef5f38b9e15 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php @@ -285,11 +285,11 @@ public function testDeepDefinitionsResolving() $this->process($container); $configurator = $container->getDefinition('sibling')->getConfigurator(); - $this->assertSame('Symfony\Component\DependencyInjection\Definition', \get_class($configurator[0])); + $this->assertSame('Symfony\Component\DependencyInjection\Definition', $configurator[0]::class); $this->assertSame('parentClass', $configurator[0]->getClass()); $factory = $container->getDefinition('sibling')->getFactory(); - $this->assertSame('Symfony\Component\DependencyInjection\Definition', \get_class($factory[0])); + $this->assertSame('Symfony\Component\DependencyInjection\Definition', $factory[0]::class); $this->assertSame('parentClass', $factory[0]->getClass()); $argument = $container->getDefinition('sibling')->getArgument(0); @@ -297,11 +297,11 @@ public function testDeepDefinitionsResolving() $this->assertSame('parentClass', $argument->getClass()); $properties = $container->getDefinition('sibling')->getProperties(); - $this->assertSame('Symfony\Component\DependencyInjection\Definition', \get_class($properties['prop'])); + $this->assertSame('Symfony\Component\DependencyInjection\Definition', $properties['prop']::class); $this->assertSame('parentClass', $properties['prop']->getClass()); $methodCalls = $container->getDefinition('sibling')->getMethodCalls(); - $this->assertSame('Symfony\Component\DependencyInjection\Definition', \get_class($methodCalls[0][1][0])); + $this->assertSame('Symfony\Component\DependencyInjection\Definition', $methodCalls[0][1][0]::class); $this->assertSame('parentClass', $methodCalls[0][1][0]->getClass()); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php index 4d248306a0b7d..7c787b0f5a263 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php @@ -79,9 +79,9 @@ public function testProcessValue() /** @var ServiceLocator $locator */ $locator = $container->get('foo'); - $this->assertSame(CustomDefinition::class, \get_class($locator('bar'))); - $this->assertSame(CustomDefinition::class, \get_class($locator('baz'))); - $this->assertSame(CustomDefinition::class, \get_class($locator('some.service'))); + $this->assertSame(CustomDefinition::class, $locator('bar')::class); + $this->assertSame(CustomDefinition::class, $locator('baz')::class); + $this->assertSame(CustomDefinition::class, $locator('some.service')::class); } public function testServiceWithKeyOverwritesPreviousInheritedKey() @@ -104,7 +104,7 @@ public function testServiceWithKeyOverwritesPreviousInheritedKey() /** @var ServiceLocator $locator */ $locator = $container->get('foo'); - $this->assertSame(TestDefinition2::class, \get_class($locator('bar'))); + $this->assertSame(TestDefinition2::class, $locator('bar')::class); } public function testInheritedKeyOverwritesPreviousServiceWithKey() @@ -128,8 +128,8 @@ public function testInheritedKeyOverwritesPreviousServiceWithKey() /** @var ServiceLocator $locator */ $locator = $container->get('foo'); - $this->assertSame(TestDefinition1::class, \get_class($locator('bar'))); - $this->assertSame(TestDefinition2::class, \get_class($locator(16))); + $this->assertSame(TestDefinition1::class, $locator('bar')::class); + $this->assertSame(TestDefinition2::class, $locator(16)::class); } public function testBindingsAreCopied() @@ -164,8 +164,8 @@ public function testTaggedServices() /** @var ServiceLocator $locator */ $locator = $container->get('foo'); - $this->assertSame(TestDefinition1::class, \get_class($locator('bar'))); - $this->assertSame(TestDefinition2::class, \get_class($locator('baz'))); + $this->assertSame(TestDefinition1::class, $locator('bar')::class); + $this->assertSame(TestDefinition2::class, $locator('baz')::class); } public function testIndexedByServiceIdWithDecoration() diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php index a45e8e1c7887e..c2f1353ff73d5 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php @@ -408,8 +408,8 @@ public function handleExceptionProvider(): array ['Uncaught Exception: foo', new \Exception('foo')], ['Uncaught Exception: foo', new class('foo') extends \RuntimeException { }], - ['Uncaught Exception: foo stdClass@anonymous bar', new \RuntimeException('foo '.\get_class(new class() extends \stdClass { - }).' bar')], + ['Uncaught Exception: foo stdClass@anonymous bar', new \RuntimeException('foo '.(new class() extends \stdClass { + })::class.' bar')], ['Uncaught Error: bar', new \Error('bar')], ['Uncaught ccc', new \ErrorException('ccc')], ]; diff --git a/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php index 298fb673d30aa..dce4658c35edf 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php @@ -249,13 +249,13 @@ public function testAnonymousClass() $this->assertSame('RuntimeException@anonymous', $flattened->getClass()); - $flattened->setClass(\get_class(new class('Oops') extends NotFoundHttpException { - })); + $flattened->setClass((new class('Oops') extends NotFoundHttpException { + })::class); $this->assertSame('Symfony\Component\HttpKernel\Exception\NotFoundHttpException@anonymous', $flattened->getClass()); - $flattened = FlattenException::createFromThrowable(new \Exception(sprintf('Class "%s" blah.', \get_class(new class() extends \RuntimeException { - })))); + $flattened = FlattenException::createFromThrowable(new \Exception(sprintf('Class "%s" blah.', (new class() extends \RuntimeException { + })::class))); $this->assertSame('Class "RuntimeException@anonymous" blah.', $flattened->getMessage()); } diff --git a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php index 91f8a3dc836bc..86468b1f70de8 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php +++ b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php @@ -134,7 +134,7 @@ private function parseListener(array $listener): array } if (\is_object($listener[0])) { - return [get_debug_type($listener[0]), \get_class($listener[0])]; + return [get_debug_type($listener[0]), $listener[0]::class]; } return [$listener[0], $listener[0]]; diff --git a/src/Symfony/Component/Form/Command/DebugCommand.php b/src/Symfony/Component/Form/Command/DebugCommand.php index afa283bbd5f15..0aadd4347a8f9 100644 --- a/src/Symfony/Component/Form/Command/DebugCommand.php +++ b/src/Symfony/Component/Form/Command/DebugCommand.php @@ -122,7 +122,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $object = $resolvedType->getOptionsResolver(); if (!$object->isDefined($option)) { - $message = sprintf('Option "%s" is not defined in "%s".', $option, \get_class($resolvedType->getInnerType())); + $message = sprintf('Option "%s" is not defined in "%s".', $option, $resolvedType->getInnerType()::class); if ($alternatives = $this->findAlternatives($option, $object->getDefinedOptions())) { if (1 === \count($alternatives)) { diff --git a/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php b/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php index 3441d017b15dd..43edf13d35b3b 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php @@ -169,7 +169,7 @@ protected function filterOptionsByDeprecated(ResolvedFormTypeInterface $type) private function getParentOptionsResolver(ResolvedFormTypeInterface $type): OptionsResolver { - $this->parents[$class = \get_class($type->getInnerType())] = []; + $this->parents[$class = $type->getInnerType()::class] = []; if (null !== $type->getParent()) { $optionsResolver = clone $this->getParentOptionsResolver($type->getParent()); diff --git a/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php index d40561e468eb3..92d600389fe08 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php @@ -51,7 +51,7 @@ protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedF $this->sortOptions($formOptions); $data = [ - 'class' => \get_class($resolvedFormType->getInnerType()), + 'class' => $resolvedFormType->getInnerType()::class, 'block_prefix' => $resolvedFormType->getInnerType()->getBlockPrefix(), 'options' => $formOptions, 'parent_types' => $this->parents, diff --git a/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php index 439526af75b35..53151574bdeb0 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php @@ -84,7 +84,7 @@ protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedF 'extension' => 'Extension options', ], $formOptions); - $this->output->title(sprintf('%s (Block prefix: "%s")', \get_class($resolvedFormType->getInnerType()), $resolvedFormType->getInnerType()->getBlockPrefix())); + $this->output->title(sprintf('%s (Block prefix: "%s")', $resolvedFormType->getInnerType()::class, $resolvedFormType->getInnerType()->getBlockPrefix())); if ($formOptions) { $this->output->table($tableHeaders, $this->buildTableRows($tableHeaders, $formOptions)); @@ -135,7 +135,7 @@ protected function describeOption(OptionsResolver $optionsResolver, array $optio } array_pop($rows); - $this->output->title(sprintf('%s (%s)', \get_class($options['type']), $options['option'])); + $this->output->title(sprintf('%s (%s)', $options['type']::class, $options['option'])); $this->output->table([], $rows); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 7b5cffe821441..e7ae6373bad01 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -135,7 +135,7 @@ public function configureOptions(OptionsResolver $resolver) // Derive "data_class" option from passed "data" object $dataClass = function (Options $options) { - return isset($options['data']) && \is_object($options['data']) ? \get_class($options['data']) : null; + return isset($options['data']) && \is_object($options['data']) ? $options['data']::class : null; }; // Derive "empty_data" closure from "data_class" option diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index ec96dc6aedfb7..bb0d04f509c1b 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -58,7 +58,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) ->addEventSubscriber(new CsrfValidationListener( $options['csrf_field_name'], $options['csrf_token_manager'], - $options['csrf_token_id'] ?: ($builder->getName() ?: \get_class($builder->getType()->getInnerType())), + $options['csrf_token_id'] ?: ($builder->getName() ?: $builder->getType()->getInnerType()::class), $options['csrf_message'], $this->translator, $this->translationDomain, @@ -74,7 +74,7 @@ public function finishView(FormView $view, FormInterface $form, array $options) { if ($options['csrf_protection'] && !$view->parent && $options['compound']) { $factory = $form->getConfig()->getFormFactory(); - $tokenId = $options['csrf_token_id'] ?: ($form->getName() ?: \get_class($form->getConfig()->getType()->getInnerType())); + $tokenId = $options['csrf_token_id'] ?: ($form->getName() ?: $form->getConfig()->getType()->getInnerType()::class); $data = (string) $options['csrf_token_manager']->getToken($tokenId); $csrfForm = $factory->createNamed($options['csrf_field_name'], HiddenType::class, $data, [ diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php index 0a9145c624e68..0fca88069b066 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php @@ -232,7 +232,7 @@ protected function getCasters(): array FormInterface::class => function (FormInterface $f, array $a) { return [ Caster::PREFIX_VIRTUAL.'name' => $f->getName(), - Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub(\get_class($f->getConfig()->getType()->getInnerType())), + Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub($f->getConfig()->getType()->getInnerType()::class), ]; }, FormView::class => StubCaster::cutInternals(...), diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php index 028e8c06d033b..158cf321092b3 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php @@ -27,7 +27,7 @@ public function extractConfiguration(FormInterface $form): array $data = [ 'id' => $this->buildId($form), 'name' => $form->getName(), - 'type_class' => \get_class($form->getConfig()->getType()->getInnerType()), + 'type_class' => $form->getConfig()->getType()->getInnerType()::class, 'synchronized' => $form->isSynchronized(), 'passed_options' => [], 'resolved_options' => [], diff --git a/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.ph 8000 p index 3f7c4005fe112..5f6556ca6ec6f 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php @@ -31,7 +31,7 @@ public function __construct(FormTypeGuesserInterface $guesser) public function addType(FormTypeInterface $type) { - $this->types[\get_class($type)] = $type; + $this->types[$type::class] = $type; } public function getType($name): FormTypeInterface diff --git a/src/Symfony/Component/Form/Tests/FormFactoryBuilderTest.php b/src/Symfony/Component/Form/Tests/FormFactoryBuilderTest.php index fd6ef52bd5f78..818ab1b2b12b0 100644 --- a/src/Symfony/Component/Form/Tests/FormFactoryBuilderTest.php +++ b/src/Symfony/Component/Form/Tests/FormFactoryBuilderTest.php @@ -40,7 +40,7 @@ public function testAddType() $extensions = $registry->getExtensions(); $this->assertCount(1, $extensions); - $this->assertTrue($extensions[0]->hasType(\get_class($this->type))); + $this->assertTrue($extensions[0]->hasType($this->type::class)); $this->assertNull($extensions[0]->getTypeGuesser()); } diff --git a/src/Symfony/Component/HttpClient/RetryableHttpClient.php b/src/Symfony/Component/HttpClient/RetryableHttpClient.php index d0c13165be59e..4dc54727565cc 100644 --- a/src/Symfony/Component/HttpClient/RetryableHttpClient.php +++ b/src/Symfony/Component/HttpClient/RetryableHttpClient.php @@ -73,7 +73,7 @@ public function request(string $method, string $url, array $options = []): Respo if ('' !== $context->getInfo('primary_ip')) { $shouldRetry = $this->strategy->shouldRetry($context, null, $exception); if (null === $shouldRetry) { - throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with an exception.', \get_class($this->strategy))); + throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with an exception.', $this->strategy::class)); } if (false === $shouldRetry) { @@ -104,7 +104,7 @@ public function request(string $method, string $url, array $options = []): Respo } if (null === $shouldRetry = $this->strategy->shouldRetry($context, $content, null)) { - throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with a body.', \get_class($this->strategy))); + throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with a body.', $this->strategy::class)); } if (false === $shouldRetry) { diff --git a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php index 23dc7b6d9f0b0..db3ce095d9df6 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php @@ -204,7 +204,7 @@ public function testRetryNetworkError() return $response; }, function (\Exception $exception) use (&$failureCallableCalled, $client) { $this->assertSame(NetworkException::class, $exception::class); - $this->assertSame(TransportException::class, \get_class($exception->getPrevious())); + $this->assertSame(TransportException::class, $exception->getPrevious()::class); $failureCallableCalled = true; return $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057')); diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php index 52ac242141af6..9930cc08bfb77 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php @@ -73,7 +73,7 @@ public function getArguments(Request $request, callable $controller, \Reflection $representative = $controller; if (\is_array($representative)) { - $representative = sprintf('%s::%s()', \get_class($representative[0]), $representative[1]); + $representative = sprintf('%s::%s()', $representative[0]::class, $representative[1]); } elseif (\is_object($representative)) { $representative = get_debug_type($representative); } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php index edc30e1806c13..0cb4703b29a16 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php @@ -42,7 +42,7 @@ public function supports(Request $request, ArgumentMetadata $argument): bool return true; } - $method = \get_class($this->inner).'::'.__FUNCTION__; + $method = $this->inner::class.'::'.__FUNCTION__; $this->stopwatch->start($method, 'controller.argument_value_resolver'); $return = $this->inner->supports($request, $argument); @@ -54,7 +54,7 @@ public function supports(Request $request, ArgumentMetadata $argument): bool public function resolve(Request $request, ArgumentMetadata $argument): iterable { - $method = \get_class($this->inner).'::'.__FUNCTION__; + $method = $this->inner::class.'::'.__FUNCTION__; $this->stopwatch->start($method, 'controller.argument_value_resolver'); yield from $this->inner->resolve($request, $argument); diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index ba921fa3b31f6..20a47dce86719 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -414,7 +414,7 @@ protected function initializeContainer() $lock = null; } elseif (!is_file($cachePath) || !\is_object($this->container = include $cachePath)) { $this->container = null; - } elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) { + } elseif (!$oldContainer || $this->container::class !== $oldContainer->name) { flock($lock, \LOCK_UN); fclose($lock); $this->container->set('kernel', $this); @@ -504,7 +504,7 @@ protected function initializeContainer() $this->container = require $cachePath; $this->container->set('kernel', $this); - if ($oldContainer && \get_class($this->container) !== $oldContainer->name) { + if ($oldContainer && $this->container::class !== $oldContainer->name) { // Because concurrent requests might still be using them, // old container files are not removed immediately, // but on a next dump of the container. diff --git a/src/Symfony/Component/HttpKernel/Profiler/Profiler.php b/src/Symfony/Component/HttpKernel/Profiler/Profiler.php index 8ee11e8e18178..884c926b834bc 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/Profiler.php +++ b/src/Symfony/Component/HttpKernel/Profiler/Profiler.php @@ -98,7 +98,7 @@ public function saveProfile(Profile $profile): bool } if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { - $this->logger->warning('Unable to store the profiler information.', ['configured_storage' => \get_class($this->storage)]); + $this->logger->warning('Unable to store the profiler information.', ['configured_storage' => $this->storage::class]); } return $ret; diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index fa127326ee839..690268c75fd3c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -99,7 +99,7 @@ public function testInitializeContainerClearsOldContainers() $kernel = new CustomProjectDirKernel(); $kernel->boot(); - $containerDir = __DIR__.'/Fixtures/var/cache/custom/'.substr(\get_class($kernel->getContainer()), 0, 16); + $containerDir = __DIR__.'/Fixtures/var/cache/custom/'.substr($kernel->getContainer()::class, 0, 16); $this->assertTrue(unlink(__DIR__.'/Fixtures/var/cache/custom/Symfony_Component_HttpKernel_Tests_CustomProjectDirKernelCustomDebugContainer.php.meta')); $this->assertFileExists($containerDir); $this->assertFileDoesNotExist($containerDir.'.legacy'); @@ -489,7 +489,7 @@ public function testKernelReset() $kernel = new CustomProjectDirKernel(); $kernel->boot(); - $containerClass = \get_class($kernel->getContainer()); + $containerClass = $kernel->getContainer()::class; $containerFile = (new \ReflectionClass($kernel->getContainer()))->getFileName(); unlink(__DIR__.'/Fixtures/var/cache/custom/Symfony_Component_HttpKernel_Tests_CustomProjectDirKernelCustomDebugContainer.php.meta'); diff --git a/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php b/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php index 5cfed46007b5e..a1bcfd9546d69 100644 --- a/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php +++ b/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php @@ -50,7 +50,7 @@ public function onCheckPassport(CheckPassportEvent $event) } if (!$passport->hasBadge(PasswordCredentials::class)) { - throw new \LogicException(sprintf('LDAP authentication requires a passport containing password credentials, authenticator "%s" does not fulfill these requirements.', \get_class($event->getAuthenticator()))); + throw new \LogicException(sprintf('LDAP authentication requires a passport containing password credentials, authenticator "%s" does not fulfill these requirements.', $event->getAuthenticator()::class)); } /** @var PasswordCredentials $passwordCredentials */ diff --git a/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php b/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php index a5fc509528249..a3a179ed54893 100644 --- a/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php +++ b/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php @@ -42,7 +42,7 @@ public function __construct(Connection|string $connOrUrl) { if ($connOrUrl instanceof Connection) { if (!$connOrUrl->getDatabasePlatform() instanceof PostgreSQLPlatform) { - throw new InvalidArgumentException(sprintf('The adapter "%s" does not support the "%s" platform.', __CLASS__, \get_class($connOrUrl->getDatabasePlatform()))); + throw new InvalidArgumentException(sprintf('The adapter "%s" does not support the "%s" platform.', __CLASS__, $connOrUrl->getDatabasePlatform()::class)); } $this->conn = $connOrUrl; } else { diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php index 441d45f503966..7a224a5c0b2c1 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Fixtures/long_receiver.php @@ -39,7 +39,7 @@ $worker = new Worker(['the_receiver' => $receiver], new class() implements MessageBusInterface { public function dispatch($envelope, array $stamps = []): Envelope { - echo 'Get envelope with message: '.get_class($envelope->getMessage())."\n"; + echo 'Get envelope with message: '.$envelope->getMessage()::class."\n"; echo sprintf("with stamps: %s\n", json_encode(array_keys($envelope->all()), \JSON_PRETTY_PRINT)); sleep(30); diff --git a/src/Symfony/Component/Messenger/Command/AbstractFailedMessagesCommand.php b/src/Symfony/Component/Messenger/Command/AbstractFailedMessagesCommand.php index 3f502e78b215e..29854786e3888 100644 --- a/src/Symfony/Component/Messenger/Command/AbstractFailedMessagesCommand.php +++ b/src/Symfony/Component/Messenger/Command/AbstractFailedMessagesCommand.php @@ -86,7 +86,7 @@ protected function displaySingleMessage(Envelope $envelope, SymfonyStyle $io) $lastMessageDecodingFailedStamp = $envelope->last(MessageDecodingFailedStamp::class); $rows = [ - ['Class', \get_class($envelope->getMessage())], + ['Class', $envelope->getMessage()::class], ]; if (null !== $id = $this->getMessageId($envelope)) { diff --git a/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php b/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php index 5a6eb1fb930d6..f64547b8f958c 100644 --- a/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php +++ b/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php @@ -99,7 +99,7 @@ private function listMessages(?string $failedTransportName, SymfonyStyle $io, in $this->phpSerializer?->acceptPhpIncompleteClass(); try { foreach ($envelopes as $envelope) { - $currentClassName = \get_class($envelope->getMessage()); + $currentClassName = $envelope->getMessage()::class; if ($classFilter && $classFilter !== $currentClassName) { continue; @@ -151,7 +151,7 @@ private function listMessagesPerClass(?string $failedTransportName, SymfonyStyle $this->phpSerializer?->acceptPhpIncompleteClass(); try { foreach ($envelopes as $envelope) { - $c = \get_class($envelope->getMessage()); + $c = $envelope->getMessage()::class; if (!isset($countPerClass[$c])) { $countPerClass[$c] = [$c, 0]; diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php index 4b6c6a2860a48..6a56f93006a02 100644 --- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php +++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php @@ -61,7 +61,7 @@ public function onMessageFailed(WorkerMessageFailedEvent $event) ); $this->logger?->info('Rejected message {class} will be sent to the failure transport {transport}.', [ - 'class' => \get_class($envelope->getMessage()), + 'class' => $envelope->getMessage()::class, 'transport' => $failureSender::class, ]); diff --git a/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php b/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php index ddb66f31eb673..293b1c26bc80e 100644 --- a/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php +++ b/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php @@ -25,7 +25,7 @@ public function __construct(Envelope $envelope, array $exceptions) { $firstFailure = current($exceptions); - $message = sprintf('Handling "%s" failed: ', \get_class($envelope->getMessage())); + $message = sprintf('Handling "%s" failed: ', $envelope->getMessage()::class); parent::__construct( $message.(1 === \count($exceptions) diff --git a/src/Symfony/Component/Messenger/Exception/ValidationFailedException.php b/src/Symfony/Component/Messenger/Exception/ValidationFailedException.php index 3e159117b2102..782355d99cd25 100644 --- a/src/Symfony/Component/Messenger/Exception/ValidationFailedException.php +++ b/src/Symfony/Component/Messenger/Exception/ValidationFailedException.php @@ -26,7 +26,7 @@ public function __construct(object $violatingMessage, ConstraintViolationListInt $this->violatingMessage = $violatingMessage; $this->violations = $violations; - parent::__construct(sprintf('Message of type "%s" failed validation.', \get_class($this->violatingMessage))); + parent::__construct(sprintf('Message of type "%s" failed validation.', $this->violatingMessage::class)); } public function getViolatingMessage() diff --git a/src/Symfony/Component/Messenger/Handler/HandlersLocator.php b/src/Symfony/Component/Messenger/Handler/HandlersLocator.php index 2bb944e0a7a29..daeacdfa82dcf 100644 --- a/src/Symfony/Component/Messenger/Handler/HandlersLocator.php +++ b/src/Symfony/Component/Messenger/Handler/HandlersLocator.php @@ -63,7 +63,7 @@ public function getHandlers(Envelope $envelope): iterable */ public static function listTypes(Envelope $envelope): array { - $class = \get_class($envelope->getMessage()); + $class = $envelope->getMessage()::class; return [$class => $class] + class_parents($class) diff --git a/src/Symfony/Component/Messenger/Middleware/SendMessageMiddleware.php b/src/Symfony/Component/Messenger/Middleware/SendMessageMiddleware.php index b56499d245a2d..c69022f1ef2bf 100644 --- a/src/Symfony/Component/Messenger/Middleware/SendMessageMiddleware.php +++ b/src/Symfony/Component/Messenger/Middleware/SendMessageMiddleware.php @@ -44,7 +44,7 @@ public function __construct(SendersLocatorInterface $sendersLocator, EventDispat public function handle(Envelope $envelope, StackInterface $stack): Envelope { $context = [ - 'class' => \get_class($envelope->getMessage()), + 'class' => $envelope->getMessage()::class, ]; $sender = null; diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php index d44d99ca00b39..443da0a67c9e7 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php @@ -101,7 +101,7 @@ public function encode(Envelope $envelope): array $envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class); - $headers = ['type' => \get_class($envelope->getMessage())] + $this->encodeStamps($envelope) + $this->getContentTypeHeader(); + $headers = ['type' => $envelope->getMessage()::class] + $this->encodeStamps($envelope) + $this->getContentTypeHeader(); return [ 'body' => $serializedMessageStamp diff --git a/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php b/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php index 028472561cce0..25a6111a39e83 100644 --- a/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php +++ b/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php @@ -43,7 +43,7 @@ public function onMessageFailed(WorkerMessageFailedEvent $event) } $envelope = $event->getEnvelope(); $notification = Notification::fromThrowable($throwable)->importance(Notification::IMPORTANCE_HIGH); - $notification->subject(sprintf('A "%s" message has just failed: %s.', \get_class($envelope->getMessage()), $notification->getSubject())); + $notification->subject(sprintf('A "%s" message has just failed: %s.', $envelope->getMessage()::class, $notification->getSubject())); $this->notifier->send($notification, ...$this->notifier->getAdminRecipients()); } diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php index 4ab19b1da8bd1..963a0f9a08327 100644 --- a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php +++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php @@ -146,14 +146,14 @@ private function executeAuthenticators(array $authenticators, Request $request): // eagerly (before token storage is initialized), whereas authenticate() is called // lazily (after initialization). if (false === $authenticator->supports($request)) { - $this->logger?->debug('Skipping the "{authenticator}" authenticator as it did not support the request.', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); + $this->logger?->debug('Skipping the "{authenticator}" authenticator as it did not support the request.', ['authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); continue; } $response = $this->executeAuthenticator($authenticator, $request); if (null !== $response) { - $this->logger?->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); + $this->logger?->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); return $response; } @@ -201,7 +201,7 @@ private function executeAuthenticator(AuthenticatorInterface $authenticator, Req $this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($authenticatedToken), AuthenticationEvents::AUTHENTICATION_SUCCESS); - $this->logger?->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); + $this->logger?->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); } catch (AuthenticationException $e) { // oh no! Authentication failed! $response = $this->handleAuthenticationFailure($e, $request, $authenticator, $passport); @@ -218,7 +218,7 @@ private function executeAuthenticator(AuthenticatorInterface $authenticator, Req return $response; } - $this->logger?->debug('Authenticator set no success response: request continues.', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); + $this->logger?->debug('Authenticator set no success response: request continues.', ['authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); return null; } @@ -243,7 +243,7 @@ private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, */ private function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $authenticator, ?Passport $passport): ?Response { - $this->logger?->info('Authenticator failed.', ['exception' => $authenticationException, 'authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); + $this->logger?->info('Authenticator failed.', ['exception' => $authenticationException, 'authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); // Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status) // to prevent user enumeration via response content comparison @@ -253,7 +253,7 @@ private function handleAuthenticationFailure(AuthenticationException $authentica $response = $authenticator->onAuthenticationFailure($request, $authenticationException); if (null !== $response && null !== $this->logger) { - $this->logger->debug('The "{authenticator}" authenticator set the failure response.', ['authenticator' => \get_class($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)]); + $this->logger->debug('The "{authenticator}" authenticator set the failure response.', ['authenticator' => ($authenticator instanceof TraceableAuthenticator ? $authenticator->getAuthenticator() : $authenticator)::class]); } $this->eventDispatcher->dispatch($loginFailureEvent = new LoginFailureEvent($authenticationException, $authenticator, $request, $response, $this->firewallName, $passport)); diff --git a/src/Symfony/Component/Security/Http/Authenticator/Debug/TraceableAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/Debug/TraceableAuthenticator.php index 1ac91ce350452..8cf865bb9e13d 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Debug/TraceableAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Debug/TraceableAuthenticator.php @@ -45,7 +45,7 @@ public function getInfo(): array 'supports' => true, 'passport' => $this->passport, 'duration' => $this->duration, - 'stub' => $this->stub ??= class_exists(ClassStub::class) ? new ClassStub(\get_class($this->authenticator)) : \get_class($this->authenticator), + 'stub' => $this->stub ??= class_exists(ClassStub::class) ? new ClassStub($this->authenticator::class) : $this->authenticator::class, ]; } diff --git a/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php b/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php index 86ca8ca8a63d6..ea4d5d561c7e2 100644 --- a/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/RememberMeListener.php @@ -47,7 +47,7 @@ public function onSuccessfulLogin(LoginSuccessEvent $event): void { $passport = $event->getPassport(); if (!$passport->hasBadge(RememberMeBadge::class)) { - $this->logger?->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($event->getAuthenticator())]); + $this->logger?->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => $event->getAuthenticator()::class]); return; } diff --git a/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php b/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php index 73c645c6c9e42..35fc462d06990 100644 --- a/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php +++ b/src/Symfony/Component/Serializer/Debug/TraceableEncoder.php @@ -44,7 +44,7 @@ public function encode(mixed $data, string $format, array $context = []): string $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectEncoding($traceId, \get_class($this->encoder), $time); + $this->dataCollector->collectEncoding($traceId, $this->encoder::class, $time); } return $encoded; @@ -70,7 +70,7 @@ public function decode(string $data, string $format, array $context = []): mixed $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectDecoding($traceId, \get_class($this->encoder), $time); + $this->dataCollector->collectDecoding($traceId, $this->encoder::class, $time); } return $encoded; diff --git a/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php b/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php index be214ce695ae5..fc801ff8ad085 100644 --- a/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php +++ b/src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php @@ -46,7 +46,7 @@ public function normalize(mixed $object, string $format = null, array $context = $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectNormalization($traceId, \get_class($this->normalizer), $time); + $this->dataCollector->collectNormalization($traceId, $this->normalizer::class, $time); } return $normalized; @@ -72,7 +72,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectDenormalization($traceId, \get_class($this->normalizer), $time); + $this->dataCollector->collectDenormalization($traceId, $this->normalizer::class, $time); } return $denormalized; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/CustomNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/CustomNormalizerTest.php index e3f13fc1f9ddd..d69ee8e40fa9f 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/CustomNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/CustomNormalizerTest.php @@ -50,11 +50,11 @@ public function testSerialize() public function testDeserialize() { - $obj = $this->normalizer->denormalize('foo', \get_class(new ScalarDummy()), 'xml'); + $obj = $this->normalizer->denormalize('foo', (new ScalarDummy())::class, 'xml'); $this->assertEquals('foo', $obj->xmlFoo); $this->assertNull($obj->foo); - $obj = $this->normalizer->denormalize('foo', \get_class(new ScalarDummy()), 'json'); + $obj = $this->normalizer->denormalize('foo', (new ScalarDummy())::class, 'json'); $this->assertEquals('foo', $obj->foo); $this->assertNull($obj->xmlFoo); } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index f7b5dc88d4108..396a2a04b497b 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -736,10 +736,10 @@ public function testDoesntHaveIssuesWithUnionConstTypes() $normalizer = new ObjectNormalizer(null, null, null, $extractor); $serializer = new Serializer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); - $this->assertSame('bar', $serializer->denormalize(['foo' => 'bar'], \get_class(new class() { + $this->assertSame('bar', $serializer->denormalize(['foo' => 'bar'], (new class() { /** @var self::*|null */ public $foo; - }))->foo); + })::class)->foo); } public function testExtractAttributesRespectsFormat() diff --git a/src/Symfony/Component/Validator/ConstraintViolation.php b/src/Symfony/Component/Validator/ConstraintViolation.php index 776bad6dc7d16..43bab60aa87df 100644 --- a/src/Symfony/Component/Validator/ConstraintViolation.php +++ b/src/Symfony/Component/Validator/ConstraintViolation.php @@ -66,7 +66,7 @@ public function __construct(string|\Stringable $message, ?string $messageTemplat public function __toString(): string { if (\is_object($this->root)) { - $class = 'Object('.\get_class($this->root).')'; + $class = 'Object('.$this->root::class.')'; } elseif (\is_array($this->root)) { $class = 'Array'; } else { diff --git a/src/Symfony/Component/Validator/Constraints/CallbackValidator.php b/src/Symfony/Component/Validator/Constraints/CallbackValidator.php index 24cb121e2b109..6de2ce54c796a 100644 --- a/src/Symfony/Component/Validator/Constraints/CallbackValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CallbackValidator.php @@ -35,7 +35,7 @@ public function validate 10000 (mixed $object, Constraint $constraint) } elseif (\is_array($method)) { if (!\is_callable($method)) { if (isset($method[0]) && \is_object($method[0])) { - $method[0] = \get_class($method[0]); + $method[0] = $method[0]::class; } throw new ConstraintDefinitionException(json_encode($method).' targeted by Callback constraint is not a valid callable.'); } diff --git a/src/Symfony/Component/Validator/DataCollector/ValidatorDataCollector.php b/src/Symfony/Component/Validator/DataCollector/ValidatorDataCollector.php index 4b3f791692fb6..acad1a1cccbff 100644 --- a/src/Symfony/Component/Validator/DataCollector/ValidatorDataCollector.php +++ b/src/Symfony/Component/Validator/DataCollector/ValidatorDataCollector.php @@ -90,7 +90,7 @@ protected function getCasters(): array FormInterface::class => function (FormInterface $f, array $a) { return [ Caster::PREFIX_VIRTUAL.'name' => $f->getName(), - Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub(\get_class($f->getConfig()->getType()->getInnerType())), + Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub($f->getConfig()->getType()->getInnerType()::class), Caster::PREFIX_VIRTUAL.'data' => $f->getData(), ]; }, diff --git a/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php index bb2b19eef204e..6ae373f37f323 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php @@ -501,7 +501,7 @@ public function uploadedFileErrorProvider() ], '1']; // access FileValidator::factorizeSizes() private method to format max file size - $reflection = new \ReflectionClass(\get_class(new FileValidator())); + $reflection = new \ReflectionClass((new FileValidator())); $method = $reflection->getMethod('factorizeSizes'); [, $limit, $suffix] = $method->invokeArgs(new FileValidator(), [0, UploadedFile::getMaxFilesize(), false]); diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php b/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php index e161af75af1bd..6e673ee9fbe8d 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php @@ -25,7 +25,7 @@ public function getMetadataFor($class): MetadataInterface if (\is_object($class)) { $hash = spl_object_hash($class); - $class = \get_class($class); + $class = $class::class; } if (!\is_string($class)) { @@ -49,7 +49,7 @@ public function hasMetadataFor($class): bool if (\is_object($class)) { $hash = spl_object_hash($class); - $class = \get_class($class); + $class = $class::class; } if (!\is_string($class)) { diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php index 7de30f4f01610..735457013caa4 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php @@ -251,8 +251,8 @@ public function testExcludeVerbosity() public function testAnonymous() { - $e = new \Exception(sprintf('Boo "%s" ba.', \get_class(new class('Foo') extends \Exception { - }))); + $e = new \Exception(sprintf('Boo "%s" ba.', (new class('Foo') extends \Exception { + })::class)); $expectedDump = <<<'EODUMP' Exception { diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php index cd6876cdff22f..6ca2dad3a48d3 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php @@ -192,8 +192,8 @@ public function testClassStubWithNotExistingMethod() public function testClassStubWithAnonymousClass() { - $var = [new ClassStub(\get_class(new class() extends \Exception { - }))]; + $var = [new ClassStub((new class() extends \Exception { + })::class)]; $cloner = new VarCloner(); $dumper = new HtmlDumper(); diff --git a/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php b/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php index 8a7ea374c90aa..de89269e7d4a9 100644 --- a/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php +++ b/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php @@ -30,19 +30,19 @@ public function __construct(LoggerInterface $logger) public function onLeave(Event $event) { foreach ($event->getTransition()->getFroms() as $place) { - $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } public function onTransition(Event $event) { - $this->logger->info(sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), $event->getSubject()::class, $event->getWorkflowName())); } public function onEnter(Event $event) { foreach ($event->getTransition()->getTos() as $place) { - $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } From 24b7cdeaa4dfb5abdd6ac57e3b01ff6587345fad Mon Sep 17 00:00:00 2001 From: Mo Ismailzai Date: Wed, 30 Nov 2022 13:08:47 -0800 Subject: [PATCH 019/475] Update UPGRADE-6.2.md edit incorrect class name --- UPGRADE-6.2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADE-6.2.md b/UPGRADE-6.2.md index 3879850dc0abc..34fb385798259 100644 --- a/UPGRADE-6.2.md +++ b/UPGRADE-6.2.md @@ -85,7 +85,7 @@ Security * Add maximum username length enforcement of 4096 characters in `UserBadge` to prevent [session storage flooding](https://symfony.com/blog/cve-2016-4423-large-username-storage-in-session) - * Deprecate the `Symfony\Component\Security\Core\Security` class and service, use `Symfony\Bundle\SecurityBundle\Security\Security` instead + * Deprecate the `Symfony\Component\Security\Core\Security` class and service, use `Symfony\Bundle\SecurityBundle\Security` instead * Passing empty username or password parameter when using `JsonLoginAuthenticator` is not supported anymore * Add `$lifetime` parameter to `LoginLinkHandlerInterface::createLoginLink()` * Change the signature of `TokenStorageInterface::setToken()` to `setToken(?TokenInterface $token)` From 695d5ebe8c523875e2f455c0142c4b7e99062391 Mon Sep 17 00:00:00 2001 From: Simon Leblanc Date: Sat, 19 Nov 2022 23:41:33 +0100 Subject: [PATCH 020/475] [Notifier] Add iSendPro bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 + .../Notifier/Bridge/Isendpro/.gitattributes | 4 + .../Notifier/Bridge/Isendpro/.gitignore | 3 + .../Notifier/Bridge/Isendpro/CHANGELOG.md | 7 ++ .../Bridge/Isendpro/IsendproTransport.php | 115 ++++++++++++++++++ .../Isendpro/IsendproTransportFactory.php | 40 ++++++ .../Notifier/Bridge/Isendpro/LICENSE | 19 +++ .../Notifier/Bridge/Isendpro/README.md | 27 ++++ .../Tests/IsendproTransportFactoryTest.php | 85 +++++++++++++ .../Isendpro/Tests/IsendproTransportTest.php | 108 ++++++++++++++++ .../Notifier/Bridge/Isendpro/composer.json | 37 ++++++ .../Notifier/Bridge/Isendpro/phpunit.xml.dist | 31 +++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 3 + src/Symfony/Component/Notifier/Transport.php | 2 + 16 files changed, 492 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/Isendpro/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 93d5de007ef9b..6f013e0d70820 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -144,6 +144,7 @@ use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Bridge\Isendpro\IsendproTransportFactory; use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; @@ -2565,6 +2566,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat', InfobipTransportFactory::class => 'notifier.transport_factory.infobip', IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', + IsendproTransportFactory::class => 'notifier.transport_factory.isendpro', KazInfoTehTransportFactory::class => 'notifier.transport_factory.kaz-info-teh', LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 237ae18a59eb7..69d0bb01bc7bb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -30,6 +30,7 @@ use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Bridge\Isendpro\IsendproTransportFactory; use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; @@ -149,6 +150,10 @@ ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.isendpro', IsendproTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->set('notifier.transport_factory.mobyt', MobytTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Isendpro/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/.gitignore b/src/Symfony/Component/Notifier/Bridge/Isendpro/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Isendpro/CHANGELOG.md new file mode 100644 index 0000000000000..1f2c8f86cde72 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransport.php b/src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransport.php new file mode 100644 index 0000000000000..938c30a2e5562 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransport.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Isendpro; + +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +final class IsendproTransport extends AbstractTransport +{ + protected const HOST = 'apirest.isendpro.com'; + + public function __construct( + #[\SensitiveParameter] private string $keyid, + private ?string $from = null, + private bool $noStop = false, + private bool $sandbox = false, + HttpClientInterface $client = null, + EventDispatcherInterface $dispatcher = null + ) { + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + if (null === $this->from) { + return sprintf('isendpro://%s?no_stop=%d&sandbox=%d', $this->getEndpoint(), (int) $this->noStop, (int) $this->sandbox); + } + + return sprintf('isendpro://%s?from=%s&no_stop=%d&sandbox=%d', $this->getEndpoint(), $this->from, (int) $this->noStop, (int) $this->sandbox); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage; + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof SmsMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + + $messageId = bin2hex(random_bytes(7)); + + $messageData = [ + 'keyid' => $this->keyid, + 'num' => $message->getPhone(), + 'sms' => $message->getSubject(), + 'sandbox' => (int) $this->sandbox, + 'tracker' => $messageId, + ]; + + if ($this->noStop) { + $messageData['nostop'] = '1'; + } + + if ('' !== $message->getFrom()) { + $messageData['emetteur'] = $message->getFrom(); + } elseif (null !== $this->from) { + $messageData['emetteur'] = $this->from; + } + + $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/cgi-bin/sms', [ + 'headers' => [ + 'Accept' => 'application/json', + 'Cache-Control' => 'no-cache', + ], + 'json' => $messageData, + ]); + + try { + $statusCode = $response->getStatusCode(); + $result = $response->toArray(false); + $details = $result['etat']['etat'][0] ?? []; + $detailsCode = (int) ($details['code'] ?? -1); // -1 is not a valid error code on iSendPro. But if code doesn't exist, it's a very strange error (not possible normally) + + if (200 === $statusCode && 0 === $detailsCode) { + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($messageId); + + return $sentMessage; + } + + $errorMessage = sprintf('Unable to send the SMS: error %d.', $statusCode); + $detailsMessage = $details['message'] ?? null; + + if ($detailsMessage) { + $errorMessage .= sprintf(' Details from iSendPro: %s: "%s".', $detailsCode, $detailsMessage); + } + } catch (TransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote iSendPro server.', $response, 0, $e); + } catch (DecodingExceptionInterface $e) { + $errorMessage = sprintf('Unable to send the SMS: error %d. %s', $statusCode, $e->getMessage()); + } + + throw new TransportException($errorMessage, $response); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransportFactory.php new file mode 100644 index 0000000000000..c91583c17e4d5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/IsendproTransportFactory.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Isendpro; + +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +final class IsendproTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): IsendproTransport + { + if ('isendpro' !== $dsn->getScheme()) { + throw new UnsupportedSchemeException($dsn, 'isendpro', $this->getSupportedSchemes()); + } + + $keyid = $this->getUser($dsn); + $from = $dsn->getOption('from', null); + $noStop = filter_var($dsn->getOption('no_stop', false), \FILTER_VALIDATE_BOOLEAN); + $sandbox = filter_var($dsn->getOption('sandbox', false), \FILTER_VALIDATE_BOOLEAN); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new IsendproTransport($keyid, $from, $noStop, $sandbox, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return ['isendpro']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE b/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE new file mode 100644 index 0000000000000..0ece8964f767d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/README.md b/src/Symfony/Component/Notifier/Bridge/Isendpro/README.md new file mode 100644 index 0000000000000..6cf8368a90362 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/README.md @@ -0,0 +1,27 @@ +iSendPro Notifier +================= + +Provides [iSendPro](https://www.isendpro.com/) integration for Symfony Notifier. + +DSN example +----------- + +``` +ISENDPRO_DSN=isendpro://ACCOUNT_KEY_ID@default?from=FROM&no_stop=NO_STOP&sandbox=SANDBOX +``` + +where: + - `ACCOUNT_KEY_ID` is your iSendPro API Key ID + - `FROM` is the alphanumeric originator for the message to appear to originate from (optional) + - `NO_STOP` setting this parameter to "1" (default "0") allows removing "STOP clause" at the end of the message for non-commercial use (optional) + - `SANDBOX` setting this parameter to "1" (default "0") allows to use the notifier in sandbox mode (optional) + +See iSendPro documentation at https://www.isendpro.com/docs/#prerequis + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportFactoryTest.php new file mode 100644 index 0000000000000..ceda78493ccd0 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportFactoryTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Isendpro\Tests; + +use Symfony\Component\Notifier\Bridge\Isendpro\IsendproTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +final class IsendproTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): IsendproTransportFactory + { + return new IsendproTransportFactory(); + } + + public function createProvider(): iterable + { + yield [ + 'isendpro://host.test?no_stop=0&sandbox=0', + 'isendpro://account_key_id@host.test', + ]; + + yield [ + 'isendpro://host.test?from=FROM&no_stop=0&sandbox=0', + 'isendpro://account_key_id@host.test?from=FROM', + ]; + + yield [ + 'isendpro://host.test?from=FROM&no_stop=0&sandbox=0', + 'isendpro://account_key_id@host.test?from=FROM&no_stop=0&sandbox=0', + ]; + + yield [ + 'isendpro://host.test?from=FROM&no_stop=0&sandbox=0', + 'isendpro://account_key_id@host.test?from=FROM&no_stop=false&sandbox=0', + ]; + + yield [ + 'isendpro://host.test?from=FROM&no_stop=1&sandbox=0', + 'isendpro://account_key_id@host.test?from=FROM&no_stop=1&sandbox=0', + ]; + + yield [ + 'isendpro://host.test?from=FROM&no_stop=1&sandbox=1', + 'isendpro://account_key_id@host.test?from=FROM&no_stop=1&sandbox=true', + ]; + } + + public function supportsProvider(): iterable + { + yield [true, 'isendpro://account_key_id@host?from=FROM']; + yield [false, 'somethingElse://account_key_id@default']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing credentials' => ['isendpro://host?from=FROM']; + } + + public function missingRequiredOptionProvider(): iterable + { + yield 'missing option: account_key_id' => ['isendpro://default']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://account_key_id@default']; + } + + /** + * @dataProvider missingRequiredOptionProvider + */ + public function testMissingRequiredOptionException(string $dsn, string $message = null) + { + $this->markTestIncomplete('The only required option is account key id, matched by incompleteDsnProvider'); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportTest.php new file mode 100644 index 0000000000000..bcafe34c5a263 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/Tests/IsendproTransportTest.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Isendpro\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Notifier\Bridge\Isendpro\IsendproTransport; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +final class IsendproTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null): IsendproTransport + { + return (new IsendproTransport('accound_key_id', null, false, false, $client ?? $this->createMock(HttpClientInterface::class)))->setHost('host.test'); + } + + public function toStringProvider(): iterable + { + yield ['isendpro://host.test?no_stop=0&sandbox=0', $this->createTransport()]; + } + + public function supportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } + + public function testSendWithErrorResponseThrowsTransportException() + { + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->exactly(2)) + ->method('getStatusCode') + ->willReturn(500); + + $client = new MockHttpClient(static function () use ($response): ResponseInterface { + return $response; + }); + + $transport = $this->createTransport($client); + + $this->expectException(TransportException::class); + $this->expectExceptionMessage('Unable to send the SMS: error 500.'); + + $transport->send(new SmsMessage('phone', 'testMessage')); + } + + public function testSendWithErrorResponseContainingDetailsThrowsTransportException() + { + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->exactly(2)) + ->method('getStatusCode') + ->willReturn(400); + $response->expects($this->once()) + ->method('getContent') + ->willReturn(json_encode(['etat' => ['etat' => [['code' => '3', 'message' => 'Your credentials are incorrect']]]])); + + $client = new MockHttpClient(static function () use ($response): ResponseInterface { + return $response; + }); + + $transport = $this->createTransport($client); + + $this->expectException(TransportException::class); + $this->expectExceptionMessage('Unable to send the SMS: error 400. Details from iSendPro: 3: "Your credentials are incorrect".'); + + $transport->send(new SmsMessage('phone', 'testMessage')); + } + + public function testSendWithSuccessfulResponseDispatchesMessageEvent() + { + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->exactly(2)) + ->method('getStatusCode') + ->willReturn(200); + $response->expects($this->once()) + ->method('getContent') + ->willReturn(json_encode(['etat' => ['etat' => [['code' => 0]]]])); + + $client = new MockHttpClient(static function () use ($response): ResponseInterface { + return $response; + }); + + $transport = $this->createTransport($client); + + $sentMessage = $transport->send(new SmsMessage('phone', 'testMessage')); + + $this->assertNotNull($sentMessage->getMessageId()); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/composer.json b/src/Symfony/Component/Notifier/Bridge/Isendpro/composer.json new file mode 100644 index 0000000000000..77b078b6dcee1 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/isendpro-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony iSendPro Notifier Bridge", + "keywords": ["sms", "isendpro", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Simon Leblanc", + "email": "contact@leblanc-simon.eu" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.3" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Isendpro\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Isendpro/phpunit.xml.dist new file mode 100644 index 0000000000000..11bcba3a27793 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Resources + ./Tests + ./vendor + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index f5b51bf9bfaaf..24232be0062a1 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -88,6 +88,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Iqsms\IqsmsTransportFactory::class, 'package' => 'symfony/iqsms-notifier', ], + 'isendpro' => [ + 'class' => Bridge\Isendpro\IsendproTransportFactory::class, + 'package' => 'symfony/isendpro-notifier', + ], 'lightsms' => [ 'class' => Bridge\LightSms\LightSmsTransportFactory::class, 'package' => 'symfony/light-sms-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 8ffb0c1f79c4c..fdf37912231fa 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -29,6 +29,7 @@ use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Bridge\Isendpro\IsendproTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; @@ -88,6 +89,7 @@ public static function setUpBeforeClass(): void GoogleChatTransportFactory::class => false, InfobipTransportFactory::class => false, IqsmsTransportFactory::class => false, + IsendproTransportFactory::class => false, LightSmsTransportFactory::class => false, LinkedInTransportFactory::class => false, MailjetTransportFactory::class => false, @@ -152,6 +154,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat yield ['googlechat', 'symfony/google-chat-notifier']; yield ['infobip', 'symfony/infobip-notifier']; yield ['iqsms', 'symfony/iqsms-notifier']; + yield ['isendpro', 'symfony/isendpro-notifier']; yield ['lightsms', 'symfony/light-sms-notifier']; yield ['linkedin', 'symfony/linked-in-notifier']; yield ['mailjet', 'symfony/mailjet-notifier']; diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index b9829b8ebdf68..c46bcc59bf2f3 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -27,6 +27,7 @@ use Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Bridge\Isendpro\IsendproTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; @@ -88,6 +89,7 @@ final class Transport GitterTransportFactory::class, InfobipTransportFactory::class, IqsmsTransportFactory::class, + IsendproTransportFactory::class, LightSmsTransportFactory::class, MailjetTransportFactory::class, MattermostTransportFactory::class, From 5dbe1fee4c0000a0517d07151e1d794a52cb0f8f Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:49:56 -0400 Subject: [PATCH 021/475] [Notifier] Add Termii bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 + .../Notifier/Bridge/Termii/.gitattributes | 4 + .../Notifier/Bridge/Termii/.gitignore | 3 + .../Notifier/Bridge/Termii/CHANGELOG.md | 7 ++ .../Component/Notifier/Bridge/Termii/LICENSE | 19 +++ .../Notifier/Bridge/Termii/README.md | 25 ++++ .../Notifier/Bridge/Termii/TermiiOptions.php | 104 +++++++++++++++ .../Bridge/Termii/TermiiTransport.php | 102 +++++++++++++++ .../Bridge/Termii/TermiiTransportFactory.php | 46 +++++++ .../Tests/TermiiTransportFactoryTest.php | 51 ++++++++ .../Termii/Tests/TermiiTransportTest.php | 119 ++++++++++++++++++ .../Notifier/Bridge/Termii/composer.json | 36 ++++++ .../Notifier/Bridge/Termii/phpunit.xml.dist | 31 +++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 1 + src/Symfony/Component/Notifier/Transport.php | 2 + 17 files changed, 561 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/TermiiOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/Termii/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 93d5de007ef9b..d2a78ae49b8f7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -171,6 +171,7 @@ use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; +use Symfony\Component\Notifier\Bridge\Termii\TermiiTransportFactory; use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransport; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; @@ -2592,6 +2593,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit', TelegramTransportFactory::class => 'notifier.transport_factory.telegram', TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', + TermiiTransportFactory::class => 'notifier.transport_factory.termii', TurboSmsTransport::class => 'notifier.transport_factory.turbo-sms', TwilioTransportFactory::class => 'notifier.transport_factory.twilio', VonageTransportFactory::class => 'notifier.transport_factory.vonage', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 237ae18a59eb7..e88e7c1a20300 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -57,6 +57,7 @@ use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; +use Symfony\Component\Notifier\Bridge\Termii\TermiiTransportFactory; use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; @@ -287,5 +288,9 @@ ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') + ->set('notifier.transport_factory.termii', TermiiTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Termii/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/.gitignore b/src/Symfony/Component/Notifier/Bridge/Termii/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Termii/CHANGELOG.md new file mode 100644 index 0000000000000..75d6a403c1334 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + +* Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE b/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE new file mode 100644 index 0000000000000..0ece8964f767d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/README.md b/src/Symfony/Component/Notifier/Bridge/Termii/README.md new file mode 100644 index 0000000000000..96bb8b0988a7c --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/README.md @@ -0,0 +1,25 @@ +Termii Notifier +============= + +Provides [Termii](https://www.termii.com) integration for Symfony Notifier. + +DSN example +----------- + +``` +TERMII_DSN=termii://API_KEY@default?from=FROM&channel=CHANNEL +``` + +where: + + - `API_KEY` is your Termii API key + - `FROM` is your sender + - `CHANNEL` is your channel (generic, dnd, whatsapp) + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/TermiiOptions.php b/src/Symfony/Component/Notifier/Bridge/Termii/TermiiOptions.php new file mode 100644 index 0000000000000..1d63a2fba57ac --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/TermiiOptions.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Termii; + +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author gnito-org + */ +final class TermiiOptions implements MessageOptionsInterface +{ + private array $options; + + public function __construct(array $options = []) + { + $this->options = $options; + } + + public function getChannel(): ?string + { + return $this->options['channel'] ?? null; + } + + public function getFrom(): ?string + { + return $this->options['from'] ?? null; + } + + public function getMediaCaption(): ?string + { + return $this->options['media_caption'] ?? null; + } + + public function getMediaUrl(): ?string + { + return $this->options['media_url'] ?? null; + } + + public function getRecipientId(): ?string + { + return $this->options['recipient_id'] ?? null; + } + + public function getType(): ?string + { + return $this->options['type'] ?? null; + } + + public function setChannel(string $channel): self + { + $this->options['channel'] = $channel; + + return $this; + } + + public function setFrom(string $from): self + { + $this->options['from'] = $from; + + return $this; + } + + public function setMediaCaption(string $mediaCaption): self + { + $this->options['media_caption'] = $mediaCaption; + + return $this; + } + + public function setMediaUrl(string $mediaUrl): self + { + $this->options['media_url'] = $mediaUrl; + + return $this; + } + + public function setRecipientId(string $id): self + { + $this->options['recipient_id'] = $id; + + return $this; + } + + public function setType(string $type): self + { + $this->options['type'] = $type; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransport.php b/src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransport.php new file mode 100644 index 0000000000000..ef9d4af5c06d6 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransport.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Termii; + +use Symfony\Component\HttpClient\Exception\JsonException; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author gnito-org + */ +final class TermiiTransport extends AbstractTransport +{ + protected const HOST = 'api.ng.termii.com'; + + public function __construct( + #[\SensitiveParameter] private readonly string $apiKey, + private readonly string $from, + private readonly string $channel, + HttpClientInterface $client = null, + EventDispatcherInterface $dispatcher = null + ) { + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('termii://%s?from=%s&channel=%s', $this->getEndpoint(), $this->from, $this->channel); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage; + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof SmsMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + + $opts = $message->getOptions(); + $options = $opts ? $opts->toArray() : []; + $options['api_key'] = $this->apiKey; + $options['sms'] = $message->getSubject(); + $options['from'] = $options['from'] ?? $this->from; + $options['to'] = $message->getPhone(); + $options['channel'] = $options['channel'] ?? $this->channel; + $options['type'] = $options['type'] ?? 'plain'; + + if (isset($options['media_url'])) { + $options['media']['url'] = $options['media_url'] ?? null; + $options['media']['caption'] = $options['media_caption'] ?? null; + unset($options['media_url'], $options['media_caption']); + } + + if (!preg_match('/^[a-zA-Z0-9\s]{3,11}$/', $options['from']) && !preg_match('/^\+?[1-9]\d{1,14}$/', $options['from'])) { + throw new InvalidArgumentException(sprintf('The "From" number "%s" is not a valid phone number, shortcode, or alphanumeric sender ID.', $this->from)); + } + + $endpoint = sprintf('https://%s/api/sms/send', $this->getEndpoint()); + $response = $this->client->request('POST', $endpoint, ['json' => array_filter($options)]); + + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote Termii server.', $response, 0, $e); + } + + if (200 !== $statusCode) { + try { + $error = $response->toArray(false); + } catch (JsonException) { + $error['message'] = $response->getContent(false); + } + throw new TransportException(sprintf('Unable to send the SMS - status code: "%s": "%s".', $statusCode, $error['message'] ?? 'unknown error'), $response); + } + + $success = $response->toArray(false); + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($success['message_id']); + + return $sentMessage; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransportFactory.php new file mode 100644 index 0000000000000..e8026f87ddcfd --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/TermiiTransportFactory.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Termii; + +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author gnito-org + */ +final class TermiiTransportFactory extends AbstractTransportFactory +{ + private const TRANSPORT_SCHEME = 'termii'; + + public function create(Dsn $dsn): TermiiTransport + { + $scheme = $dsn->getScheme(); + + if (self::TRANSPORT_SCHEME !== $scheme) { + throw new UnsupportedSchemeException($dsn, self::TRANSPORT_SCHEME, $this->getSupportedSchemes()); + } + + $apiKey = $this->getUser($dsn); + $from = $dsn->getRequiredOption('from'); + $channel = $dsn->getRequiredOption('channel'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new TermiiTransport($apiKey, $from, $channel, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return [self::TRANSPORT_SCHEME]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportFactoryTest.php new file mode 100644 index 0000000000000..d831141c88f85 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportFactoryTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Termii\Tests; + +use Symfony\Component\Notifier\Bridge\Termii\TermiiTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +final class TermiiTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): TermiiTransportFactory + { + return new TermiiTransportFactory(); + } + + public function createProvider(): iterable + { + yield ['termii://host.test?from=0611223344&channel=generic', 'termii://apiKey@host.test?from=0611223344&channel=generic']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing auth ID' => ['termii://@default?from=FROM']; + } + + public function missingRequiredOptionProvider(): iterable + { + yield 'missing option: from' => ['termii://apiKey@default?channel=generic']; + yield 'missing option: channel' => ['termii://apiKey@default?from=0611223344']; + } + + public function supportsProvider(): iterable + { + yield [true, 'termii://apiKey@default?from=0611223344&channel=generic']; + yield [false, 'somethingElse://apiKey@default?from=0611223344&channel=generic']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://apiKey@default?from=0611223344&channel=generic']; + yield ['somethingElse://apiKey@default']; // missing "from" option + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportTest.php new file mode 100644 index 0000000000000..8d140f6637de7 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/Tests/TermiiTransportTest.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Termii\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Notifier\Bridge\Termii\TermiiTransport; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +final class TermiiTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null, string $from = 'from'): TermiiTransport + { + return new TermiiTransport('apiKey', $from, 'generic', $client ?? $this->createMock(HttpClientInterface::class)); + } + + public function invalidFromProvider(): iterable + { + yield 'too short' => ['aa']; + yield 'too long' => ['abcdefghijkl']; + yield 'no zero at start if phone number' => ['+0']; + yield 'phone number too short' => ['+1']; + } + + public function supportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + } + + /** + * @dataProvider invalidFromProvider + */ + public function testInvalidArgumentExceptionIsThrownIfFromIsInvalid(string $from) + { + $transport = $this->createTransport(null, $from); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('The "From" number "%s" is not a valid phone number, shortcode, or alphanumeric sender ID.', $from)); + + $transport->send(new SmsMessage('+33612345678', 'Hello!')); + } + + /** + * @dataProvider validFromProvider + */ + public function testNoInvalidArgumentExceptionIsThrownIfFromIsValid(string $from) + { + $message = new SmsMessage('+33612345678', 'Hello!'); + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::exactly(2))->method('getStatusCode')->willReturn(200); + $response->expects(self::once())->method('getContent')->willReturn(json_encode(['message' => 'Successfully sent', 'message_id' => 'foo', 'balance' => 9, 'user' => 'Foo Bar'])); + $client = new MockHttpClient(function (string $method, string $url) use ($response): ResponseInterface { + self::assertSame('POST', $method); + self::assertSame('https://api.ng.termii.com/api/sms/send', $url); + + return $response; + } + ); + $transport = $this->createTransport($client, $from); + $sentMessage = $transport->send($message); + + self::assertSame('foo', $sentMessage->getMessageId()); + } + + public function toStringProvider(): iterable + { + yield ['termii://api.ng.termii.com?from=from&channel=generic', $this->createTransport()]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } + + public function validFromProvider(): iterable + { + yield ['abc']; + yield ['abcd']; + yield ['abcde']; + yield ['abcdef']; + yield ['abcdefg']; + yield ['abcdefgh']; + yield ['abcdefghi']; + yield ['abcdefghij']; + yield ['abcdefghijk']; + yield ['abcdef ghij']; + yield [' abcdefghij']; + yield ['abcdefghij ']; + yield ['+11']; + yield ['+112']; + yield ['+1123']; + yield ['+11234']; + yield ['+112345']; + yield ['+1123456']; + yield ['+11234567']; + yield ['+112345678']; + yield ['+1123456789']; + yield ['+11234567891']; + yield ['+112345678912']; + yield ['+1123456789123']; + yield ['+11234567891234']; + yield ['+112345678912345']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/composer.json b/src/Symfony/Component/Notifier/Bridge/Termii/composer.json new file mode 100644 index 0000000000000..b09c504cfbf0a --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/termii-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony Termii Notifier Bridge", + "keywords": [ + "termii", + "notifier" + ], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "gnito-org", + "homepage": "https://github.com/gnito-org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.3" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0" + }, + "autoload": { + "psr-4": {"Symfony\\Component\\Notifier\\Bridge\\Termii\\": ""}, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Termii/phpunit.xml.dist new file mode 100644 index 0000000000000..33dc8c12fe5da --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Termii/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index f5b51bf9bfaaf..7151c25870434 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -196,6 +196,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Telnyx\TelnyxTransportFactory::class, 'package' => 'symfony/telnyx-notifier', ], + 'termii' => [ + 'class' => Bridge\Termii\TermiiTransportFactory::class, + 'package' => 'symfony/termii-notifier', + ], 'turbosms' => [ 'class' => Bridge\TurboSms\TurboSmsTransportFactory::class, 'package' => 'symfony/turbo-sms-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 8ffb0c1f79c4c..a66ef56887246 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -177,6 +177,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat yield ['spothit', 'symfony/spot-hit-notifier']; yield ['telegram', 'symfony/telegram-notifier']; yield ['telnyx', 'symfony/telnyx-notifier']; + yield ['termii', 'symfony/termii-notifier']; yield ['turbosms', 'symfony/turbo-sms-notifier']; yield ['twilio', 'symfony/twilio-notifier']; yield ['zendesk', 'symfony/zendesk-notifier']; diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index b9829b8ebdf68..3141cb1cf2b02 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -49,6 +49,7 @@ use Symfony\Component\Notifier\Bridge\SmsFactor\SmsFactorTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; +use Symfony\Component\Notifier\Bridge\Termii\TermiiTr 10000 ansportFactory; use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; @@ -110,6 +111,7 @@ final class Transport SmsFactorTransportFactory::class, TelegramTransportFactory::class, TelnyxTransportFactory::class, + TermiiTransportFactory::class, TurboSmsTransportFactory::class, TwilioTransportFactory::class, VonageTransportFactory::class, From c67274c0dcf08f8b455f1895b9259f9f93501782 Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:18:16 -0400 Subject: [PATCH 022/475] [Notifier] Add RingCentral bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 9 +- .../Bridge/RingCentral/.gitattributes | 4 + .../Notifier/Bridge/RingCentral/.gitignore | 3 + .../Notifier/Bridge/RingCentral/CHANGELOG.md | 7 ++ .../Notifier/Bridge/RingCentral/LICENSE | 19 +++ .../Notifier/Bridge/RingCentral/README.md | 24 ++++ .../Bridge/RingCentral/RingCentralOptions.php | 116 ++++++++++++++++++ .../RingCentral/RingCentralTransport.php | 96 +++++++++++++++ .../RingCentralTransportFactory.php | 43 +++++++ .../Tests/RingCentralTransportFactoryTest.php | 50 ++++++++ .../Tests/RingCentralTransportTest.php | 105 ++++++++++++++++ .../Notifier/Bridge/RingCentral/composer.json | 36 ++++++ .../Bridge/RingCentral/phpunit.xml.dist | 31 +++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 1 + src/Symfony/Component/Notifier/Transport.php | 2 + 17 files changed, 550 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/RingCentral/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 7043ace6956d2..db3bfd0c101e3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -159,6 +159,7 @@ use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory as SendinblueNotifierTransportFactory; @@ -2583,6 +2584,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', OrangeSmsTransportFactory::class => 'notifier.transport_factory.orange-sms', OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', + RingCentralTransportFactory::class => 'notifier.transport_factory.ring-central', RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', SendberryTransportFactory::class => 'notifier.transport_factory.sendberry', SendinblueNotifierTransportFactory::class => 'notifier.transport_factory.sendinblue', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 80fd112574b3e..2040d6dbc6dac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -45,6 +45,7 @@ use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory; @@ -295,12 +296,16 @@ ->tag('chatter.transport_factory') ->set('notifier.transport_factory.chatwork', ChatworkTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') ->set('notifier.transport_factory.termii', TermiiTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.ring-central', RingCentralTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/.gitattributes b/src/Symfony/Component/Notifier/Bridge/RingCentral/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/.gitignore b/src/Symfony/Component/Notifier/Bridge/RingCentral/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/RingCentral/CHANGELOG.md new file mode 100644 index 0000000000000..75d6a403c1334 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + +* Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE b/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE new file mode 100644 index 0000000000000..0ece8964f767d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/README.md b/src/Symfony/Component/Notifier/Bridge/RingCentral/README.md new file mode 100644 index 0000000000000..c12cb0aa0e7fc --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/README.md @@ -0,0 +1,24 @@ +Ring Central Notifier +===================== + +Provides [Ring Central](https://www.ringcentral.com) integration for Symfony Notifier. + +DSN example +----------- + +``` +RINGCENTRAL_DSN=ringcentral://API_TOKEN@default?from=FROM +``` + +where: + + - `API_TOKEN` is your Ring Central OAuth 2 token + - `FROM` is your sender + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralOptions.php b/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralOptions.php new file mode 100644 index 0000000000000..f496feb9a9e59 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralOptions.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\RingCentral; + +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author gnito-org + */ +final class RingCentralOptions implements MessageOptionsInterface +{ + private array $options; + + public function __construct(array $options = []) + { + $this->options = $options; + } + + public function getCountryCallingCode(): ?string + { + return $this->options['country_calling_code'] ?? null; + } + + public function getCountryId(): ?string + { + return $this->options['country_id'] ?? null; + } + + public function getCountryIsoCode(): ?string + { + return $this->options['country_iso_code'] ?? null; + } + + public function getCountryName(): ?string + { + return $this->options['country_name'] ?? null; + } + + public function getCountryUri(): ?string + { + return $this->options['country_uri'] ?? null; + } + + public function getFrom(): ?string + { + return $this->options['from'] ?? null; + } + + public function getRecipientId(): ?string + { + return $this->options['recipient_id'] ?? null; + } + + public function setCountryCallingCode(string $countryCallingCode): self + { + $this->options['country_calling_code'] = $countryCallingCode; + + return $this; + } + + public function setCountryId(string $countryId): self + { + $this->options['country_id'] = $countryId; + + return $this; + } + + public function setCountryIsoCode(string $countryIsoCode): self + { + $this->options['country_iso_code'] = $countryIsoCode; + + return $this; + } + + public function setCountryName(string $countryName): self + { + $this->options['country_name'] = $countryName; + + return $this; + } + + public function setCountryUri(string $countryUri): self + { + $this->options['country_uri'] = $countryUri; + + return $this; + } + + public function setFrom(string $from): self + { + $this->options['from'] = $from; + + return $this; + } + + public function setRecipientId(string $id): self + { + $this->options['recipient_id'] = $id; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransport.php b/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransport.php new file mode 100644 index 0000000000000..add1a8b2a7ce2 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransport.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\RingCentral; + +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author gnito-org + */ +final class RingCentralTransport extends AbstractTransport +{ + protected const HOST = 'platform.ringcentral.com'; + + public function __construct( + #[\SensitiveParameter] private readonly string $apiToken, + private readonly string $from, + HttpClientInterface $client = null, + EventDispatcherInterface $dispatcher = null + ) { + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('ringcentral://%s?from=%s', $this->getEndpoint(), $this->from); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage; + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof SmsMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + + $opts = $message->getOptions(); + $options = $opts ? $opts->toArray() : []; + $options['text'] = $message->getSubject(); + $options['from']['phoneNumber'] = $options['from'] ?? $this->from; + $options['to'][]['phoneNumber'] = $message->getPhone(); + + if (isset($options['country_id'])) { + $options['country']['id'] = $options['country_id'] ?? null; + $options['country']['uri'] = $options['country_uri'] ?? null; + $options['country']['name'] = $options['country_name'] ?? null; + $options['country']['isoCode'] = $options['country_iso_code'] ?? null; + $options['country']['callingCode'] = $options['country_calling_code'] ?? null; + unset($options['country_id'], $options['country_uri'], $options['country_name'], $options['country_iso_code'], $options['country_calling_code']); + } + + if (!preg_match('/^\+[1-9]\d{1,14}$/', $options['from']['phoneNumber'])) { + throw new InvalidArgumentException(sprintf('The "From" number "%s" is not a valid phone number. Phone number must be in E.164 format.', $this->from)); + } + + $endpoint = sprintf('https://%s/restapi/v1.0/account/~/extension/~/sms', $this->getEndpoint()); + $response = $this->client->request('POST', $endpoint, ['auth_bearer' => $this->apiToken, 'json' => array_filter($options)]); + + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote Ring Central server.', $response, 0, $e); + } + + if (200 !== $statusCode) { + $error = $response->toArray(false); + throw new TransportException(sprintf('Unable to send the SMS - "%s".', $error['message'] ?? $error['error_description'] ?? $error['description'] ?? 'unknown failure'), $response); + } + + $success = $response->toArray(false); + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($success['id']); + + return $sentMessage; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransportFactory.php new file mode 100644 index 0000000000000..3859db57b0471 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/RingCentralTransportFactory.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\RingCentral; + +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author gnito-org + */ +final class RingCentralTransportFactory extends AbstractTransportFactory +{ + private const TRANSPORT_SCHEME = 'ringcentral'; + + public function create(Dsn $dsn): RingCentralTransport + { + $scheme = $dsn->getScheme(); + if (self::TRANSPORT_SCHEME !== $scheme) { + throw new UnsupportedSchemeException($dsn, self::TRANSPORT_SCHEME, $this->getSupportedSchemes()); + } + $apiKey = $this->getUser($dsn); + $from = $dsn->getRequiredOption('from'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new RingCentralTransport($apiKey, $from, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return [self::TRANSPORT_SCHEME]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportFactoryTest.php new file mode 100644 index 0000000000000..636aed7e70fd8 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportFactoryTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\RingCentral\Tests; + +use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +final class RingCentralTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): RingCentralTransportFactory + { + return new RingCentralTransportFactory(); + } + + public function createProvider(): iterable + { + yield ['ringcentral://host.test?from=0611223344', 'ringcentral://apiToken@host.test?from=0611223344']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing auth token' => ['ringcentral://@default?from=FROM']; + } + + public function missingRequiredOptionProvider(): iterable + { + yield 'missing option: from' => ['ringcentral://apiToken@default']; + } + + public function supportsProvider(): iterable + { + yield [true, 'ringcentral://apiToken@default?from=0611223344']; + yield [false, 'somethingElse://apiToken@default?from=0611223344']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://apiToken@default?from=0611223344']; + yield ['somethingElse://apiToken@default']; // missing "from" option + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportTest.php b/src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportTest.php new file mode 100644 index 0000000000000..0dbc4d8c1a7ee --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/Tests/RingCentralTransportTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\RingCentral\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransport; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +final class RingCentralTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null, string $from = 'from'): RingCentralTransport + { + return new RingCentralTransport('apiToken', $from, $client ?? $this->createMock(HttpClientInterface::class)); + } + + public function invalidFromProvider(): iterable + { + yield 'no zero at start if phone number' => ['+0']; + yield 'phone number too short' => ['+1']; + } + + public function supportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + } + + /** + * @dataProvider invalidFromProvider + */ + public function testInvalidArgumentExceptionIsThrownIfFromIsInvalid(string $from) + { + $transport = $this->createTransport(null, $from); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('The "From" number "%s" is not a valid phone number.', $from)); + + $transport->send(new SmsMessage('+33612345678', 'Hello!')); + } + + /** + * @dataProvider validFromProvider + */ + public function testNoInvalidArgumentExceptionIsThrownIfFromIsValid(string $from) + { + $message = new SmsMessage('+33612345678', 'Hello!'); + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::exactly(2))->method('getStatusCode')->willReturn(200); + $response->expects(self::once())->method('getContent')->willReturn(json_encode(['id' => 'foo'])); + $client = new MockHttpClient(function (string $method, string $url) use ($response): ResponseInterface { + self::assertSame('POST', $method); + self::assertSame('https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~/sms', $url); + + return $response; + } + ); + $transport = $this->createTransport($client, $from); + $sentMessage = $transport->send($message); + + self::assertSame('foo', $sentMessage->getMessageId()); + } + + public function toStringProvider(): iterable + { + yield ['ringcentral://platform.ringcentral.com?from=from', $this->createTransport()]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } + + public function validFromProvider(): iterable + { + yield ['+11']; + yield ['+112']; + yield ['+1123']; + yield ['+11234']; + yield ['+112345']; + yield ['+1123456']; + yield ['+11234567']; + yield ['+112345678']; + yield ['+1123456789']; + yield ['+11234567891']; + yield ['+112345678912']; + yield ['+1123456789123']; + yield ['+11234567891234']; + yield ['+112345678912345']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/composer.json b/src/Symfony/Component/Notifier/Bridge/RingCentral/composer.json new file mode 100644 index 0000000000000..f7ddcd047ebe0 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/ring-central-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony RingCentral Notifier Bridge", + "keywords": [ + "ring-central", + "notifier" + ], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "gnito-org", + "homepage": "https://github.com/gnito-org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.3" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0" + }, + "autoload": { + "psr-4": {"Symfony\\Component\\Notifier\\Bridge\\RingCentral\\": ""}, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/RingCentral/phpunit.xml.dist new file mode 100644 index 0000000000000..b570792cc3937 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index 1a1329a1efcd8..a0fc0540ae937 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -144,6 +144,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\OvhCloud\OvhCloudTransportFactory::class, 'package' => 'symfony/ovh-cloud-notifier', ], + 'ringcentral' => [ + 'class' => Bridge\RingCentral\RingCentralTransportFactory::class, + 'package' => 'symfony/ring-central-notifier', + ], 'rocketchat' => [ 'class' => Bridge\RocketChat\RocketChatTransportFactory::class, 'package' => 'symfony/rocket-chat-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index ad64ec6e61e73..c4d89e096e794 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -169,6 +169,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat yield ['octopush', 'symfony/octopush-notifier']; yield ['onesignal', 'symfony/one-signal-notifier']; yield ['ovhcloud', 'symfony/ovh-cloud-notifier']; + yield ['ringcentral', 'symfony/ring-central-notifier']; yield ['rocketchat', 'symfony/rocket-chat-notifier']; yield ['sendberry', 'symfony/sendberry-notifier']; yield ['sendinblue', 'symfony/sendinblue-notifier']; diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index 6b7dfa0eb8523..ca4201ef026ab 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -38,6 +38,7 @@ use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory; @@ -102,6 +103,7 @@ final class Transport OctopushTransportFactory::class, OrangeSmsTransportFactory::class, OvhCloudTransportFactory::class, + RingCentralTransportFactory::class, RocketChatTransportFactory::class, SendberryTransportFactory::class, SendinblueTransportFactory::class, From 72e8416446a3e58b69c6321860407415ea1b6aca Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Tue, 29 Nov 2022 13:11:18 -0400 Subject: [PATCH 023/475] [Notifier] Add Plivo bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 + .../Notifier/Bridge/Plivo/.gitattributes | 4 + .../Notifier/Bridge/Plivo/.gitignore | 3 + .../Notifier/Bridge/Plivo/CHANGELOG.md | 7 + .../Component/Notifier/Bridge/Plivo/LICENSE | 19 +++ .../Notifier/Bridge/Plivo/PlivoOptions.php | 140 ++++++++++++++++++ .../Notifier/Bridge/Plivo/PlivoTransport.php | 93 ++++++++++++ .../Bridge/Plivo/PlivoTransportFactory.php | 46 ++++++ .../Component/Notifier/Bridge/Plivo/README.md | 25 ++++ .../Plivo/Tests/PlivoTransportFactoryTest.php | 50 +++++++ .../Bridge/Plivo/Tests/PlivoTransportTest.php | 120 +++++++++++++++ .../Notifier/Bridge/Plivo/composer.json | 36 +++++ .../Notifier/Bridge/Plivo/phpunit.xml.dist | 31 ++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 1 + src/Symfony/Component/Notifier/Transport.php | 2 + 17 files changed, 588 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/PlivoOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/Plivo/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index db3bfd0c101e3..3fff07c8f03d6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -159,6 +159,7 @@ use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransportFactory; use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; @@ -2584,6 +2585,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', OrangeSmsTransportFactory::class => 'notifier.transport_factory.orange-sms', OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', + PlivoTransportFactory::class => 'notifier.transport_factory.plivo', RingCentralTransportFactory::class => 'notifier.transport_factory.ring-central', RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', SendberryTransportFactory::class => 'notifier.transport_factory.sendberry', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 2040d6dbc6dac..643e6cf4e58fb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -45,6 +45,7 @@ use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransportFactory; use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; @@ -307,5 +308,9 @@ ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.plivo', PlivoTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Plivo/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/.gitignore b/src/Symfony/Component/Notifier/Bridge/Plivo/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Plivo/CHANGELOG.md new file mode 100644 index 0000000000000..75d6a403c1334 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + +* Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE b/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE new file mode 100644 index 0000000000000..0ece8964f767d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoOptions.php b/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoOptions.php new file mode 100644 index 0000000000000..50318a91928c0 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoOptions.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Plivo; + +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author gnito-org + */ +final class PlivoOptions implements MessageOptionsInterface +{ + private array $options; + + public function __construct(array $options = []) + { + $this->options = $options; + } + + public function getLog(): ?bool + { + return $this->options['log'] ?? null; + } + + public function getMediaUrls(): ?string + { + return $this->options['media_urls'] ?? null; + } + + public function getMethod(): ?string + { + return $this->options['method'] ?? null; + } + + public function getPowerpackUuid(): ?string + { + return $this->options['powerpack_uuid'] ?? null; + } + + public function getRecipientId(): ?string + { + return $this->options['recipient_id'] ?? null; + } + + public function getSrc(): ?string + { + return $this->options['src'] ?? null; + } + + public function getTrackable(): ?bool + { + return $this->options['trackable'] ?? null; + } + + public function getType(): ?string + { + return $this->options['type'] ?? null; + } + + public function getUrl(): ?string + { + return $this->options['url'] ?? null; + } + + public function setLog(bool $log): self + { + $this->options['log'] = $log; + + return $this; + } + + public function setMediaUrls(string $mediaUrls): self + { + $this->options['media_urls'] = $mediaUrls; + + return $this; + } + + public function setMethod(string $method): self + { + $this->options['method'] = $method; + + return $this; + } + + public function setPowerpackUuid(string $powerpackUuid): self + { + $this->options['powerpack_uuid'] = $powerpackUuid; + + return $this; + } + + public function setRecipientId(string $id): self + { + $this->options['recipient_id'] = $id; + + return $this; + } + + public function setSrc(string $src): self + { + $this->options['src'] = $src; + + return $this; + } + + public function setTrackable(bool $trackable): self + { + $this->options['trackable'] = $trackable; + + return $this; + } + + public function setType(string $type): self + { + $this->options['type'] = $type; + + return $this; + } + + public function setUrl(string $url): self + { + $this->options['url'] = $url; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransport.php b/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransport.php new file mode 100644 index 0000000000000..b5984cabfc377 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransport.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Plivo; + +use Symfony\Component\HttpClient\Exception\JsonException; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author gnito-org + */ +final class PlivoTransport extends AbstractTransport +{ + protected const HOST = 'api.plivo.com'; + + public function __construct( + private readonly string $authId, + #[\SensitiveParameter] private readonly string $authToken, + private readonly string $from, + HttpClientInterface $client = null, + EventDispatcherInterface $dispatcher = null + ) { + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('plivo://%s?from=%s', $this->getEndpoint(), $this->from); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage; + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof SmsMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + + $opts = $message->getOptions(); + $options = $opts ? $opts->toArray() : []; + $options['text'] = $message->getSubject(); + $options['src'] = $options['src'] ?? $this->from; + $options['dst'] = $options['dst'] ?? $message->getPhone(); + + if (!preg_match('/^[a-zA-Z0-9\s]{2,11}$/', $options['src']) && !preg_match('/^\+?[1-9]\d{1,14}$/', $options['src'])) { + throw new InvalidArgumentException(sprintf('The "From" number "%s" is not a valid phone number, shortcode, or alphanumeric sender ID. Phone number must contain only numbers and optional + character.', $this->from)); + } + + $endpoint = sprintf('https://%s/v1/Account/%s/Message/', $this->getEndpoint(), $this->authId); + $response = $this->client->request('POST', $endpoint, ['auth_basic' => $this->authId.':'.$this->authToken, 'json' => array_filter($options)]); + + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote Plivo server.', $response, 0, $e); + } + + if (202 !== $statusCode) { + try { + $error = $response->toArray(false); + } catch (JsonException) { + $error['error'] = $response->getContent(false); + } + throw new TransportException(sprintf('Unable to send the SMS - status code: "%s": "%s".', $statusCode, $error['error'] ?? 'unknown error'), $response); + } + + $success = $response->toArray(false); + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($success['message_uuid'][0]); + + return $sentMessage; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransportFactory.php new file mode 100644 index 0000000000000..7e389b0672867 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/PlivoTransportFactory.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Plivo; + +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author gnito-org + */ +final class PlivoTransportFactory extends AbstractTransportFactory +{ + private const TRANSPORT_SCHEME = 'plivo'; + + public function create(Dsn $dsn): PlivoTransport + { + $scheme = $dsn->getScheme(); + + if (self::TRANSPORT_SCHEME !== $scheme) { + throw new UnsupportedSchemeException($dsn, self::TRANSPORT_SCHEME, $this->getSupportedSchemes()); + } + + $authId = $this->getUser($dsn); + $authToken = $this->getPassword($dsn); + $from = $dsn->getRequiredOption('from'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new PlivoTransport($authId, $authToken, $from, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return [self::TRANSPORT_SCHEME]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/README.md b/src/Symfony/Component/Notifier/Bridge/Plivo/README.md new file mode 100644 index 0000000000000..ec6a5ee23f584 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/README.md @@ -0,0 +1,25 @@ +Plivo Notifier +============== + +Provides [Plivo](https://www.plivo.com) integration for Symfony Notifier. + +DSN example +----------- + +``` +PLIVO_DSN=plivo://AUTH_ID:AUTH_TOKEN@default?from=FROM +``` + +where: + +- `AUTH_ID` is your Plivo Auth ID +- `AUTH_TOKEN` is your Plivo Auth Token +- `FROM` is your sender + +Resources +--------- + +* [Contributing](https://symfony.com/doc/current/contributing/index.html) +* [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportFactoryTest.php new file mode 100644 index 0000000000000..fa5d29f5e3f83 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportFactoryTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Plivo\Tests; + +use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +final class PlivoTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): PlivoTransportFactory + { + return new PlivoTransportFactory(); + } + + public function createProvider(): iterable + { + yield ['plivo://host.test?from=0611223344', 'plivo://authId:authToken@host.test?from=0611223344']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing auth token' => ['plivo://authId@default?from=FROM']; + } + + public function missingRequiredOptionProvider(): iterable + { + yield 'missing option: from' => ['plivo://authId:authToken@default']; + } + + public function supportsProvider(): iterable + { + yield [true, 'plivo://authId:authToken@default?from=0611223344']; + yield [false, 'somethingElse://authId:authToken@default?from=0611223344']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://authId:authToken@default?from=0611223344']; + yield ['somethingElse://authId:authToken@default']; // missing "from" option + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportTest.php new file mode 100644 index 0000000000000..9dec4768842de --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/Tests/PlivoTransportTest.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Plivo\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransport; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +final class PlivoTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null, string $from = 'from'): PlivoTransport + { + return new PlivoTransport('authId', 'authToken', $from, $client ?? $this->createMock(HttpClientInterface::class)); + } + + public function invalidFromProvider(): iterable + { + yield 'too short' => ['a']; + yield 'too long' => ['abcdefghijkl']; + yield 'no zero at start if phone number' => ['+0']; + yield 'phone number too short' => ['+1']; + } + + public function supportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + } + + /** + * @dataProvider invalidFromProvider + */ + public function testInvalidArgumentExceptionIsThrownIfFromIsInvalid(string $from) + { + $transport = $this->createTransport(null, $from); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('The "From" number "%s" is not a valid phone number, shortcode, or alphanumeric sender ID.', $from)); + + $transport->send(new SmsMessage('+33612345678', 'Hello!')); + } + + /** + * @dataProvider validFromProvider + */ + public function testNoInvalidArgumentExceptionIsThrownIfFromIsValid(string $from) + { + $message = new SmsMessage('+33612345678', 'Hello!'); + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::exactly(2))->method('getStatusCode')->willReturn(202); + $response->expects(self::once())->method('getContent')->willReturn(json_encode(['message' => 'message(s) queued', 'message_uuid' => ['foo'], 'api_id' => 'bar'])); + $client = new MockHttpClient(function (string $method, string $url) use ($response): ResponseInterface { + self::assertSame('POST', $method); + self::assertSame('https://api.plivo.com/v1/Account/authId/Message/', $url); + + return $response; + } + ); + $transport = $this->createTransport($client, $from); + $sentMessage = $transport->send($message); + + self::assertSame('foo', $sentMessage->getMessageId()); + } + + public function toStringProvider(): iterable + { + yield ['plivo://api.plivo.com?from=from', $this->createTransport()]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } + + public function validFromProvider(): iterable + { + yield ['ab']; + yield ['abc']; + yield ['abcd']; + yield ['abcde']; + yield ['abcdef']; + yield ['abcdefg']; + yield ['abcdefgh']; + yield ['abcdefghi']; + yield ['abcdefghij']; + yield ['abcdefghijk']; + yield ['abcdef ghij']; + yield [' abcdefghij']; + yield ['abcdefghij ']; + yield ['+11']; + yield ['+112']; + yield ['+1123']; + yield ['+11234']; + yield ['+112345']; + yield ['+1123456']; + yield ['+11234567']; + yield ['+112345678']; + yield ['+1123456789']; + yield ['+11234567891']; + yield ['+112345678912']; + yield ['+1123456789123']; + yield ['+11234567891234']; + yield ['+112345678912345']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/composer.json b/src/Symfony/Component/Notifier/Bridge/Plivo/composer.json new file mode 100644 index 0000000000000..d2b921a98f544 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/plivo-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony Plivo Notifier Bridge", + "keywords": [ + "plivo", + "notifier" + ], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "gnito-org", + "homepage": "https://github.com/gnito-org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.3" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0" + }, + "autoload": { + "psr-4": {"Symfony\\Component\\Notifier\\Bridge\\Plivo\\": ""}, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Plivo/phpunit.xml.dist new file mode 100644 index 0000000000000..cc84e156ea321 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index a0fc0540ae937..b44a43f751172 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -144,6 +144,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\OvhCloud\OvhCloudTransportFactory::class, 'package' => 'symfony/ovh-cloud-notifier', ], + 'plivo' => [ + 'class' => Bridge\Plivo\PlivoTransportFactory::class, + 'package' => 'symfony/plivo-notifier', + ], 'ringcentral' => [ 'class' => Bridge\RingCentral\RingCentralTransportFactory::class, 'package' => 'symfony/ring-central-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index c4d89e096e794..91e47a967a1e0 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -169,6 +169,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat yield ['octopush', 'symfony/octopush-notifier']; yield ['onesignal', 'symfony/one-signal-notifier']; yield ['ovhcloud', 'symfony/ovh-cloud-notifier']; + yield ['plivo', 'symfony/plivo-notifier']; yield ['ringcentral', 'symfony/ring-central-notifier']; yield ['rocketchat', 'symfony/rocket-chat-notifier']; yield ['sendberry', 'symfony/sendberry-notifier']; diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index ca4201ef026ab..f4e9bc61a4bcc 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -38,6 +38,7 @@ use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransportFactory; use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; @@ -103,6 +104,7 @@ final class Transport OctopushTransportFactory::class, OrangeSmsTransportFactory::class, OvhCloudTransportFactory::class, + PlivoTransportFactory::class, RingCentralTransportFactory::class, RocketChatTransportFactory::class, SendberryTransportFactory::class, From 604fd9f590b39d7f55c44146eb6be8d3a44c9a6d Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Tue, 29 Nov 2022 09:37:05 -0400 Subject: [PATCH 024/475] [Notifier] Add Bandwidth bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 + .../Notifier/Bridge/Bandwidth/.gitattributes | 4 + .../Notifier/Bridge/Bandwidth/.gitignore | 3 + .../Bridge/Bandwidth/BandwidthOptions.php | 140 ++++++++++++++++++ .../Bridge/Bandwidth/BandwidthTransport.php | 108 ++++++++++++++ .../Bandwidth/BandwidthTransportFactory.php | 49 ++++++ .../Notifier/Bridge/Bandwidth/CHANGELOG.md | 7 + .../Notifier/Bridge/Bandwidth/LICENSE | 19 +++ .../Notifier/Bridge/Bandwidth/README.md | 28 ++++ .../Tests/BandwidthTransportFactoryTest.php | 53 +++++++ .../Tests/BandwidthTransportTest.php | 103 +++++++++++++ .../Notifier/Bridge/Bandwidth/composer.json | 36 +++++ .../Bridge/Bandwidth/phpunit.xml.dist | 31 ++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 3 + src/Symfony/Component/Notifier/Transport.php | 2 + 17 files changed, 597 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/Bandwidth/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 93d5de007ef9b..d381ac1ac0a04 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -127,6 +127,7 @@ use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; +use Symfony\Component\Notifier\Bridge\Bandwidth\BandwidthTransportFactory; use Symfony\Component\Notifier\Bridge\Chatwork\ChatworkTransportFactory; use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; use Symfony\Component\Notifier\Bridge\ContactEveryone\ContactEveryoneTransportFactory; @@ -2548,6 +2549,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ $classToServices = [ AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms', AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', + BandwidthTransportFactory::class => 'notifier.transport_factory.bandwidth', ChatworkTransportFactory::class => 'notifier.transport_factory.chatwork', ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', ContactEveryoneTransportFactory::class => 'notifier.transport_factory.contact-everyone', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 237ae18a59eb7..877725416d082 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -13,6 +13,7 @@ use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; +use Symfony\Component\Notifier\Bridge\Bandwidth\BandwidthTransportFactory; use Symfony\Component\Notifier\Bridge\Chatwork\ChatworkTransportFactory; use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; use Symfony\Component\Notifier\Bridge\ContactEveryone\ContactEveryoneTransportFactory; @@ -287,5 +288,9 @@ ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') + ->set('notifier.transport_factory.bandwidth', BandwidthTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Bandwidth/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/Ban 57AE dwidth/.gitignore b/src/Symfony/Component/Notifier/Bridge/Bandwidth/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthOptions.php b/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthOptions.php new file mode 100644 index 0000000000000..86fc7103daded --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthOptions.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Bandwidth; + +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author gnito-org + */ +final class BandwidthOptions implements MessageOptionsInterface +{ + private array $options; + + public function __construct(array $options = []) + { + $this->options = $options; + } + + public function getAccountId(): ?string + { + return $this->options['account_id'] ?? null; + } + + public function getApplicationId(): ?string + { + return $this->options['application_id'] ?? null; + } + + public function getExpiration(): ?string + { + return $this->options['expiration'] ?? null; + } + + public function getFrom(): ?string + { + return $this->options['from'] ?? null; + } + + public function getMedia(): ?array + { + return $this->options['media'] ?? null; + } + + public function getPriority(): ?string + { + return $this->options['priority'] ?? null; + } + + public function getRecipientId(): ?string + { + return $this->options['recipient_id'] ?? null; + } + + public function getTag(): ?string + { + return $this->options['tag'] ?? null; + } + + public function getTo(): ?array + { + return $this->options['to'] ?? null; + } + + public function setAccountId(string $accountId): self + { + $this->options['account_id'] = $accountId; + + return $this; + } + + public function setApplicationId(string $applicationId): self + { + $this->options['application_id'] = $applicationId; + + return $this; + } + + public function setExpiration(string $expiration): self + { + $this->options['expiration'] = $expiration; + + return $this; + } + + public function setFrom(string $from): self + { + $this->options['from'] = $from; + + return $this; + } + + public function setMedia(array $media): self + { + $this->options['media'] = $media; + + return $this; + } + + public function setPriority(string $priority): self + { + $this->options['priority'] = $priority; + + return $this; + } + + public function setRecipientId(string $id): self + { + $this->options['recipient_id'] = $id; + + return $this; + } + + public function setTag(string $tag): self + { + $this->options['tag'] = $tag; + + return $this; + } + + public function setTo(array $to): self + { + $this->options['to'] = $to; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransport.php b/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransport.php new file mode 100644 index 0000000000000..913c16099d4e2 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransport.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Bandwidth; + +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author gnito-org + */ +final class BandwidthTransport extends AbstractTransport +{ + protected const HOST = 'messaging.bandwidth.com'; + + public function __construct( + private readonly string $username, + #[\SensitiveParameter] private readonly string $password, + private readonly string $from, + private readonly string $accountId, + private readonly string $applicationId, + private readonly ?string $priority, + HttpClientInterface $client = null, + EventDispatcherInterface $dispatcher = null + ) { + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('bandwidth://%s?from=%s&account_id=%s&application_id=%s', $this->getEndpoint(), $this->from, $this->accountId, $this->applicationId).($this->priority ? sprintf('&priority=%s', $this->priority) : null); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage; + } + + /** + * https://dev.bandwidth.com/apis/messaging/#tag/Messages/operation/createMessage. + */ + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof SmsMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + $opts = $message->getOptions(); + $options = $opts ? $opts->toArray() : []; + $options['text'] = $message->getSubject(); + $options['from'] = $options['from'] ?? $this->from; + $options['to'] = $options['to'] ?? [$message->getPhone()]; + $options['account_id'] = $options['account_id'] ?? $this->accountId; + $options['applicationId'] = $options['application_id'] ?? $this->applicationId; + unset($options['application_id']); + + if (!isset($options['priority']) && $this->priority) { + $options['priority'] = $this->priority; + } + + if (!preg_match('/^\+[1-9]\d{1,14}$/', $this->from)) { + throw new InvalidArgumentException(sprintf('The "From" number "%s" is not a valid phone number. The number must be in E.164 format.', $this->from)); + } + + if (!preg_match('/^\+[1-9]\d{1,14}$/', $message->getPhone())) { + throw new InvalidArgumentException(sprintf('The "To" number "%s" is not a valid phone number. The number must be in E.164 format.', $message->getPhone())); + } + $endpoint = sprintf('https://%s/api/v2/users/%s/messages', $this->getEndpoint(), $options['account_id']); + unset($options['accountId']); + + $response = $this->client->request('POST', $endpoint, [ + 'auth_basic' => $this->username.':'.$this->password, + 'json' => array_filter($options), + ]); + + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote Bandwidth server.', $response, 0, $e); + } + + if (202 !== $statusCode) { + $error = $response->toArray(false); + throw new TransportException(sprintf('Unable to send the SMS - "%s" - "%s".', $error['type'], $error['description']), $response); + } + + $success = $response->toArray(false); + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($success['id']); + + return $sentMessage; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransportFactory.php new file mode 100644 index 0000000000000..030f3b20d8b53 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/BandwidthTransportFactory.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Bandwidth; + +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author gnito-org + */ +final class BandwidthTransportFactory extends AbstractTransportFactory +{ + private const TRANSPORT_SCHEME = 'bandwidth'; + + public function create(Dsn $dsn): BandwidthTransport + { + $scheme = $dsn->getScheme(); + + if (self::TRANSPORT_SCHEME !== $scheme) { + throw new UnsupportedSchemeException($dsn, self::TRANSPORT_SCHEME, $this->getSupportedSchemes()); + } + + $username = $this->getUser($dsn); + $password = $this->getPassword($dsn); + $from = $dsn->getRequiredOption('from'); + $accountId = $dsn->getRequiredOption('account_id'); + $applicationId = $dsn->getRequiredOption('application_id'); + $priority = $dsn->getOption('priority'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new BandwidthTransport($username, $password, $from, $accountId, $applicationId, $priority, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return [self::TRANSPORT_SCHEME]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Bandwidth/CHANGELOG.md new file mode 100644 index 0000000000000..75d6a403c1334 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + +* Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE b/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE new file mode 100644 index 0000000000000..0ece8964f767d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/README.md b/src/Symfony/Component/Notifier/Bridge/Bandwidth/README.md new file mode 100644 index 0000000000000..40b2607aba895 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/README.md @@ -0,0 +1,28 @@ +Bandwidth Notifier +================== + +Provides [Bandwidth](https://www.bandwidth.com) integration for Symfony Notifier. + +DSN example +----------- + +``` +BANDWIDTH_DSN=bandwidth://USERNAME:PASSWORD@default?from=FROM&account_id=ACCOUNT_ID&application_id=APPLICATION_ID&priority=PRIORITY +``` + +where: + +- `USERNAME` is your Bandwidth username +- `PASSWORD` is your Bandwidth password +- `FROM` is your sender +- `ACCOUNT_ID` is your account ID +- `APPLICATION_ID` is your application ID +- `PRIORITY` is your priority (optional) + +Resources +--------- + +* [Contributing](https://symfony.com/doc/current/contributing/index.html) +* [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportFactoryTest.php new file mode 100644 index 0000000000000..e9115f0887e51 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportFactoryTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Bandwidth\Tests; + +use Symfony\Component\Notifier\Bridge\Bandwidth\BandwidthTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +final class BandwidthTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): BandwidthTransportFactory + { + return new BandwidthTransportFactory(); + } + + public function createProvider(): iterable + { + yield ['bandwidth://host.test?from=0611223344&account_id=account_id&application_id=application_id&priority=priority', 'bandwidth://username:password@host.test?from=0611223344&account_id=account_id&application_id=application_id&priority=priority']; + yield ['bandwidth://host.test?from=0611223344&account_id=account_id&application_id=application_id', 'bandwidth://username:password@host.test?from=0611223344&account_id=account_id&application_id=application_id']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing password' => ['bandwidth://username@default?account_id=account_id&application_id=application_id&priority=priority']; + } + + public function missingRequiredOptionProvider(): iterable + { + yield 'missing option: from' => ['bandwidth://username:password@default?account_id=account_id&application_id=application_id&priority=priority']; + yield 'missing option: account_id' => ['bandwidth://username:password@default?from=0611223344&application_id=application_id&priority=priority']; + yield 'missing option: application_id' => ['bandwidth://username:password@default?from=0611223344&account_id=account_id&priority=priority']; + } + + public function supportsProvider(): iterable + { + yield [true, 'bandwidth://username:password@default?from=0611223344&account_id=account_id&application_id=application_id&priority=priority']; + yield [false, 'somethingElse://username:password@default?from=0611223344&account_id=account_id&application_id=application_id&priority=priority']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://username:password@default?from=0611223344&account_id=account_id&application_id=application_id&priority=priority']; + yield ['somethingElse://username:password@default?account_id=account_id&application_id=application_id&priority=priority']; // missing "from" option + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportTest.php new file mode 100644 index 0000000000000..23533452ff695 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/Tests/BandwidthTransportTest.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Bandwidth\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Notifier\Bridge\Bandwidth\BandwidthTransport; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +final class BandwidthTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null, string $from = 'from'): BandwidthTransport + { + return new BandwidthTransport('username', 'password', $from, 'account_id', 'application_id', 'priority', $client ?? $this->createMock(HttpClientInterface::class)); + } + + public function invalidFromProvider(): iterable + { + yield 'no zero at start if phone number' => ['+0']; + yield 'phone number too short' => ['+1']; + } + + public function supportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + } + + /** + * @dataProvider invalidFromProvider + */ + public function testInvalidArgumentExceptionIsThrownIfFromIsInvalid(string $from) + { + $transport = $this->createTransport(null, $from); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('The "From" number "%s" is not a valid phone number. The number must be in E.164 format.', $from)); + + $transport->send(new SmsMessage('+33612345678', 'Hello!')); + } + + /** + * @dataProvider validFromProvider + */ + public function testNoInvalidArgumentExceptionIsThrownIfFromIsValid(string $from) + { + $message = new SmsMessage('+33612345678', 'Hello!'); + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::exactly(2))->method('getStatusCode')->willReturn(202); + $response->expects(self::once())->method('getContent')->willReturn(json_encode(['id' => 'foo'])); + $client = new MockHttpClient(function (string $method, string $url) use ($response): ResponseInterface { + self::assertSame('POST', $method); + self::assertSame('https://messaging.bandwidth.com/api/v2/users/account_id/messages', $url); + + return $response; + }); + $transport = $this->createTransport($client, $from); + $sentMessage = $transport->send($message); + self::assertSame('foo', $sentMessage->getMessageId()); + } + + public function toStringProvider(): iterable + { + yield ['bandwidth://messaging.bandwidth.com?from=from&account_id=account_id&application_id=application_id&priority=priority', $this->createTransport()]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } + + public function validFromProvider(): iterable + { + yield ['+11']; + yield ['+112']; + yield ['+1123']; + yield ['+11234']; + yield ['+112345']; + yield ['+1123456']; + yield ['+11234567']; + yield ['+112345678']; + yield ['+1123456789']; + yield ['+11234567891']; + yield ['+112345678912']; + yield ['+1123456789123']; + yield ['+11234567891234']; + yield ['+112345678912345']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/composer.json b/src/Symfony/Component/Notifier/Bridge/Bandwidth/composer.json new file mode 100644 index 0000000000000..60ca552aa57b8 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/bandwidth-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony Bandwidth Notifier Bridge", + "keywords": [ + "bandwidth", + "notifier" + ], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "gnito-org", + "homepage": "https://github.com/gnito-org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.3" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0" + }, + "autoload": { + "psr-4": {"Symfony\\Component\\Notifier\\Bridge\\Bandwidth\\": ""}, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Bandwidth/phpunit.xml.dist new file mode 100644 index 0000000000000..1371b1f96f7ec --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index f5b51bf9bfaaf..e86ef5dd7d18e 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -24,6 +24,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\AllMySms\AllMySmsTransportFactory::class, 'package' => 'symfony/all-my-sms-notifier', ], + 'bandwidth' => [ + 'class' => Bridge\Bandwidth\BandwidthTransportFactory::class, + 'package' => 'symfony/bandwidth-notifier', + ], 'clickatell' => [ 'class' => Bridge\Clickatell\ClickatellTransportFactory::class, 'package' => 'symfony/clickatell-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 8ffb0c1f79c4c..e9e5df4e02bc5 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -15,6 +15,7 @@ use Symfony\Bridge\PhpUnit\ClassExistsMock; use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; +use Symfony\Component\Notifier\Bridge\Bandwidth\BandwidthTransportFactory; use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; use Symfony\Component\Notifier\Bridge\ContactEveryone\ContactEveryoneTransportFactory; use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; @@ -74,6 +75,7 @@ public static function setUpBeforeClass(): void ClassExistsMock::withMockedClasses([ AllMySmsTransportFactory::class => false, AmazonSnsTransportFactory::class => false, + BandwidthTransportFactory::class => false, ClickatellTransportFactory::class => false, ContactEveryoneTransportFactory::class => false, DiscordTransportFactory::class => false, @@ -139,6 +141,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat { yield ['allmysms', 'symfony/all-my-sms-notifier']; yield ['sns', 'symfony/amazon-sns-notifier']; + yield ['bandwidth', 'symfony/bandwidth-notifier']; yield ['clickatell', 'symfony/clickatell-notifier']; yield ['contact-everyone', 'symfony/contact-everyone-notifier']; yield ['discord', 'symfony/discord-notifier']; diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index b9829b8ebdf68..d1ee2bebc1f08 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -13,6 +13,7 @@ use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; +use Symfony\Component\Notifier\Bridge\Bandwidth\BandwidthTransportFactory; use Symfony\Component\Notifier\Bridge\Chatwork\ChatworkTransportFactory; use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; use Symfony\Component\Notifier\Bridge\ContactEveryone\ContactEveryoneTransportFactory; @@ -74,6 +75,7 @@ final class Transport private const FACTORY_CLASSES = [ AllMySmsTransportFactory::class, AmazonSnsTransportFactory::class, + BandwidthTransportFactory::class, ChatworkTransportFactory::class, ClickatellTransportFactory::class, ContactEveryoneTransportFactory::class, From 527d29cdc5ee4ddbb57046b777b07adc59c6ce1b Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Sat, 3 Dec 2022 14:59:45 +0100 Subject: [PATCH 025/475] [FrameworkBundle] Don't suggest deprecated sensio/generator-bundle --- .../EventListener/SuggestMissingPackageSubscriber.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php b/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php index 53cae12ebbcff..d7bdc8e6684f9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php +++ b/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php @@ -32,9 +32,6 @@ final class SuggestMissingPackageSubscriber implements EventSubscriberInterface 'mongodb' => ['DoctrineMongoDBBundle', 'doctrine/mongodb-odm-bundle'], '_default' => ['Doctrine ORM', 'symfony/orm-pack'], ], - 'generate' => [ - '_default' => ['SensioGeneratorBundle', 'sensio/generator-bundle'], - ], 'make' => [ '_default' => ['MakerBundle', 'symfony/maker-bundle --dev'], ], From 3de59c9b8009116d7300e7de2274a014427c971e Mon Sep 17 00:00:00 2001 From: BASAK Semih Date: Sat, 3 Dec 2022 14:11:00 +0100 Subject: [PATCH 026/475] feat: using coalescing operator in file function --- .../Bundle/FrameworkBundle/Controller/AbstractController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index a8857d52f3f58..cef10efcd1a29 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -166,7 +166,7 @@ protected function json(mixed $data, int $status = 200, array $headers = [], arr protected function file(\SplFileInfo|string $file, string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse { $response = new BinaryFileResponse($file); - $response->setContentDisposition($disposition, null === $fileName ? $response->getFile()->getFilename() : $fileName); + $response->setContentDisposition($disposition, $fileName ?? $response->getFile()->getFilename()); return $response; } From 79d406a06cd06107796bee6124cddd1cfd1efc83 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sun, 4 Dec 2022 19:09:48 +0100 Subject: [PATCH 027/475] fix tests --- .../Tests/Exception/UnsupportedSchemeExceptionTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 0af7287f60b6a..2b33e41f95d11 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -43,6 +43,8 @@ use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransportFactory; +use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory; @@ -56,6 +58,7 @@ use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; +use Symfony\Component\Notifier\Bridge\Termii\TermiiTransportFactory; use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Twitter\TwitterTransportFactory; @@ -105,6 +108,8 @@ public static function setUpBeforeClass(): void OctopushTransportFactory::class => false, OneSignalTransportFactory::class => false, OvhCloudTransportFactory::class => false, + PlivoTransportFactory::class => false, + RingCentralTransportFactory::class => false, RocketChatTransportFactory::class => false, SendberryTransportFactory::class => false, SendinblueTransportFactory::class => false, @@ -118,6 +123,7 @@ public static function setUpBeforeClass(): void SpotHitTransportFactory::class => false, TelegramTransportFactory::class => false, TelnyxTransportFactory::class => false, + TermiiTransportFactory::class => false, TurboSmsTransportFactory::class => false, TwilioTransportFactory::class => false, TwitterTransportFactory::class => false, From e96524a2fdd3f5c365f150369d8ae5893c7501b7 Mon Sep 17 00:00:00 2001 From: kurozumi Date: Sun, 4 Dec 2022 11:11:43 +0900 Subject: [PATCH 028/475] [Notifier] Add Line bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 4 + .../Notifier/Bridge/LineNotify/.gitattributes | 4 + .../Notifier/Bridge/LineNotify/.gitignore | 3 + .../Notifier/Bridge/LineNotify/CHANGELOG.md | 7 ++ .../Notifier/Bridge/LineNotify/LICENSE | 19 +++++ .../Bridge/LineNotify/LineNotifyTransport.php | 81 ++++++++++++++++++ .../LineNotify/LineNotifyTransportFactory.php | 42 ++++++++++ .../Notifier/Bridge/LineNotify/README.md | 19 +++++ .../Tests/LineNotifyTransportFactoryTest.php | 48 +++++++++++ .../Tests/LineNotifyTransportTest.php | 82 +++++++++++++++++++ .../Notifier/Bridge/LineNotify/composer.json | 33 ++++++++ .../Bridge/LineNotify/phpunit.xml.dist | 31 +++++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 3 + 15 files changed, 382 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/LineNotify/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index bf09eea257c9a..b68c48dab4f2d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -149,6 +149,7 @@ use Symfony\Component\Notifier\Bridge\Isendpro\IsendproTransportFactory; use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; +use Symfony\Component\Notifier\Bridge\LineNotify\LineNotifyTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory as MailjetNotifierTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; @@ -2576,6 +2577,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ IsendproTransportFactory::class => 'notifier.transport_factory.isendpro', KazInfoTehTransportFactory::class => 'notifier.transport_factory.kaz-info-teh', LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', + LineNotifyTransportFactory::class => 'notifier.transport_factory.line-notify', LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', MailjetNotifierTransportFactory::class => 'notifier.transport_factory.mailjet', MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index cdcd05ac2aca0..0c7e55d56ee5a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -34,6 +34,7 @@ use Symfony\Component\Notifier\Bridge\Isendpro\IsendproTransportFactory; use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; +use Symfony\Component\Notifier\Bridge\LineNotify\LineNotifyTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; @@ -317,5 +318,8 @@ ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.line-notify', LineNotifyTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/.gitattributes b/src/Symfony/Component/Notifier/Bridge/LineNotify/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/.gitignore b/src/Symfony/Component/Notifier/Bridge/LineNotify/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/LineNotify/CHANGELOG.md new file mode 100644 index 0000000000000..75d6a403c1334 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + +* Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE b/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE new file mode 100644 index 0000000000000..0ece8964f767d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransport.php b/src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransport.php new file mode 100644 index 0000000000000..d4c14653d6e0d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransport.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\LineNotify; + +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Akira Kurozumi + */ +final class LineNotifyTransport extends AbstractTransport +{ + protected const HOST = 'notify-api.line.me'; + + private string $token; + + public function __construct(#[\SensitiveParameter] string $token, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + $this->token = $token; + parent::__construct($client, $dispatcher); + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof ChatMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); + } + + $content = $message->getSubject(); + + $endpoint = sprintf('https://%s/api/notify', $this->getEndpoint()); + $response = $this->client->request('POST', $endpoint, [ + 'auth_bearer' => $this->token, + 'query' => [ + 'message' => $content, + ], + ]); + + try { + $statusCode = $response->getStatusCode(); + } catch (TransportException $e) { + throw new TransportException('Could not reach the remote Line server.', $response, 0, $e); + } + + if (200 !== $statusCode) { + $result = $response->toArray(false); + + $originalContent = $message->getSubject(); + $errorCode = $result['status']; + $errorMessage = trim($result['message']); + throw new TransportException(sprintf('Unable to post the Line message: "%s" (%d: "%s").', $originalContent, $errorCode, $errorMessage), $response); + } + + return new SentMessage($message, (string) $this); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof ChatMessage; + } + + public function __toString(): string + { + return sprintf('linenotify://%s', $this->getEndpoint()); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransportFactory.php new file mode 100644 index 0000000000000..bb0d89c321740 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/LineNotifyTransportFactory.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\LineNotify; + +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author Akira Kurozumi + */ +final class LineNotifyTransportFactory extends AbstractTransportFactory +{ + private const SCHEME = 'linenotify'; + + protected function getSupportedSchemes(): array + { + return [self::SCHEME]; + } + + public function create(Dsn $dsn): LineNotifyTransport + { + if (self::SCHEME !== $dsn->getScheme()) { + throw new UnsupportedSchemeException($dsn, self::SCHEME, $this->getSupportedSchemes()); + } + + $token = $this->getUser($dsn); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new LineNotifyTransport($token, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/README.md b/src/Symfony/Component/Notifier/Bridge/LineNotify/README.md new file mode 100644 index 0000000000000..9209ea0e78f06 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/README.md @@ -0,0 +1,19 @@ +LINE Notifier +============= + +Provides [LINE Notify](https://notify-bot.line.me/) integration for Symfony Notifier. + +DSN example +----------- + +``` +LINE_NOTIFY_DSN=linenotify://TOKEN@default +``` + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportFactoryTest.php new file mode 100644 index 0000000000000..703cfe15aa8ab --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportFactoryTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Notifier\Bridge\LineNotify\LineNotifyTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +/** + * @author Akira Kurozumi + */ +final class LineNotifyTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): LineNotifyTransportFactory + { + return new LineNotifyTransportFactory(); + } + + public function supportsProvider(): iterable + { + yield [true, 'linenotify://host']; + yield [false, 'somethingElse://host']; + } + + public function createProvider(): iterable + { + yield [ + 'linenotify://host.test', + 'linenotify://token@host.test', + ]; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing token' => ['linenotify://host.test']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://token@host']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php b/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php new file mode 100644 index 0000000000000..d315e1b377533 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\LineNotify\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Notifier\Bridge\LineNotify\LineNotifyTransport; +use Symfony\Component\Notifier\Exception\LengthException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Akira Kurozumi + */ +final class LineNotifyTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null): LineNotifyTransport + { + return (new LineNotifyTransport('testToken', $client ?? $this->createMock(HttpClientInterface::class)))->setHost('host.test'); + } + + public function toStringProvider(): iterable + { + yield ['linenotify://host.test', $this->createTransport()]; + } + + public function supportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } + + public function testSendChatMessageWithMoreThan2000CharsThrowsLogicException() + { + $transport = $this->createTransport(); + + $this->expectException(LengthException::class); + $this->expectExceptionMessage('The subject length of a Line message must not exceed 1000 characters.'); + + $transport->send(new ChatMessage(str_repeat('囍', 1001))); + } + + public function testSendWithErrorResponseThrows() + { + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->exactly(2)) + ->method('getStatusCode') + ->willReturn(400); + $response->expects($this->once()) + ->method('getContent') + ->willReturn(json_encode(['message' => 'testDescription', 'code' => 'testErrorCode'])); + + $client = new MockHttpClient(static function () use ($response): ResponseInterface { + return $response; + }); + + $transport = $this->createTransport($client); + + $this->expectException(TransportException::class); + $this->expectExceptionMessageMatches('/testDescription.+testErrorCode/'); + + $transport->send(new ChatMessage('testMessage')); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/composer.json b/src/Symfony/Component/Notifier/Bridge/LineNotify/composer.json new file mode 100644 index 0000000000000..43a173bf48812 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/line-notifier", + "type": "symfony-notifier-bridge", + "description": "Provides LINE Notify integration for Symfony Notifier.", + "keywords": ["line", "notifier", "chat"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Akira Kurozumi", + "email": "info@a-zumi.net" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.3" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\LineNotify\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/LineNotify/phpunit.xml.dist new file mode 100644 index 0000000000000..6fb26783f338b --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index 4ccf388ebb766..28e7d971b1cd6 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -100,6 +100,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\LightSms\LightSmsTransportFactory::class, 'package' => 'symfony/light-sms-notifier', ], + 'linenotify' => [ + 'class' => Bridge\LineNotify\LineNotifyTransportFactory::class, + 'package' => 'symfony/line-notify-notifier', + ], 'linkedin' => [ 'class' => Bridge\LinkedIn\LinkedInTransportFactory::class, 'package' => 'symfony/linked-in-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 2b33e41f95d11..f97a2ca25a833 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -32,6 +32,7 @@ use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; use Symfony\Component\Notifier\Bridge\Isendpro\IsendproTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; +use Symfony\Component\Notifier\Bridge\LineNotify\LineNotifyTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; @@ -97,6 +98,7 @@ public static function setUpBeforeClass(): void IqsmsTransportFactory::class => false, IsendproTransportFactory::class => false, LightSmsTransportFactory::class => false, + LineNotifyTransportFactory::class => false, LinkedInTransportFactory::class => false, MailjetTransportFactory::class => false, MattermostTransportFactory::class => false, @@ -167,6 +169,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat yield ['iqsms', 'symfony/iqsms-notifier']; yield ['isendpro', 'symfony/isendpro-notifier']; yield ['lightsms', 'symfony/light-sms-notifier']; + yield ['linenotify', 'symfony/line-notify-notifier']; yield ['linkedin', 'symfony/linked-in-notifier']; yield ['mailjet', 'symfony/mailjet-notifier']; yield ['mattermost', 'symfony/mattermost-notifier']; From 77f19f510177910159456bc9764f9e11c231913c Mon Sep 17 00:00:00 2001 From: Alex Plekhanov Date: Mon, 28 Nov 2022 21:21:26 +0100 Subject: [PATCH 029/475] [Notifier] [Telegram] Add support to answer callback queries --- .../Notifier/Bridge/Telegram/CHANGELOG.md | 5 ++ .../Bridge/Telegram/TelegramOptions.php | 15 ++++ .../Bridge/Telegram/TelegramTransport.php | 27 ++++++- .../Telegram/Tests/TelegramOptionsTest.php | 73 +++++++++++++++++++ .../Telegram/Tests/TelegramTransportTest.php | 31 ++++++++ 5 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramOptionsTest.php diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Telegram/CHANGELOG.md index d0d4723934749..ec7a2a9e8ba35 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + +* Add support to answer callback queries + 5.3 --- diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php index bbc2285b634aa..aa4604d378fb5 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramOptions.php @@ -109,4 +109,19 @@ public function edit(int $messageId): static return $this; } + + /** + * @return $this + */ + public function answerCallbackQuery(string $callbackQueryId, bool $showAlert = false, int $cacheTime = 0): static + { + $this->options['callback_query_id'] = $callbackQueryId; + $this->options['show_alert'] = $showAlert; + + if ($cacheTime > 0) { + $this->options['cache_time'] = $cacheTime; + } + + return $this; + } } diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php index 9a027b1ac2b32..a3f60009f30e9 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php @@ -84,8 +84,7 @@ protected function doSend(MessageInterface $message): SentMessage $options['text'] = preg_replace('/([_*\[\]()~`>#+\-=|{}.!])/', '\\\\$1', $message->getSubject()); } - $path = isset($options['message_id']) ? 'editMessageText' : 'sendMessage'; - $endpoint = sprintf('https://%s/bot%s/%s', $this->getEndpoint(), $this->token, $path); + $endpoint = sprintf('https://%s/bot%s/%s', $this->getEndpoint(), $this->token, $this->getPath($options)); $response = $this->client->request('POST', $endpoint, [ 'json' => array_filter($options), @@ -100,14 +99,34 @@ protected function doSend(MessageInterface $message): SentMessage if (200 !== $statusCode) { $result = $response->toArray(false); - throw new TransportException('Unable to '.(isset($options['message_id']) ? 'edit' : 'post').' the Telegram message: '.$result['description'].sprintf(' (code %d).', $result['error_code']), $response); + throw new TransportException('Unable to '.$this->getAction($options).' the Telegram message: '.$result['description'].sprintf(' (code %d).', $result['error_code']), $response); } $success = $response->toArray(false); $sentMessage = new SentMessage($message, (string) $this); - $sentMessage->setMessageId($success['result']['message_id']); + if (isset($success['result']['message_id'])) { + $sentMessage->setMessageId($success['result']['message_id']); + } return $sentMessage; } + + private function getPath(array $options): string + { + return match (true) { + isset($options['message_id']) => 'editMessageText', + isset($options['callback_query_id']) => 'answerCallbackQuery', + default => 'sendMessage', + }; + } + + private function getAction(array $options): string + { + return match (true) { + isset($options['message_id']) => 'edit', + isset($options['callback_query_id']) => 'answer callback query', + default => 'post', + }; + } } diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramOptionsTest.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramOptionsTest.php new file mode 100644 index 0000000000000..20183bd9af631 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramOptionsTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Telegram\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Notifier\Bridge\Telegram\TelegramOptions; + +final class TelegramOptionsTest extends TestCase +{ + /** + * @dataProvider validCacheTimeDataProvider + */ + public function testAnswerCallbackQueryWithCacheTime(int $cacheTime) + { + $options = new TelegramOptions(); + + $returnedOptions = $options->answerCallbackQuery('123', true, $cacheTime); + + $this->assertSame($options, $returnedOptions); + $this->assertEquals( + [ + 'callback_query_id' => '123', + 'show_alert' => true, + 'cache_time' => $cacheTime, + ], + $options->toArray(), + ); + } + + public function validCacheTimeDataProvider(): iterable + { + yield 'cache time equals 1' => [1]; + yield 'cache time equals 2' => [2]; + yield 'cache time equals 10' => [10]; + } + + /** + * @dataProvider invalidCacheTimeDataProvider + */ + public function testAnswerCallbackQuery(int $cacheTime) + { + $options = new TelegramOptions(); + + $returnedOptions = $options->answerCallbackQuery('123', true, $cacheTime); + + $this->assertSame($options, $returnedOptions); + $this->assertEquals( + [ + 'callback_query_id' => '123', + 'show_alert' => true, + ], + $options->toArray(), + ); + } + + public function invalidCacheTimeDataProvider(): iterable + { + yield 'cache time equals 0' => [0]; + yield 'cache time equals -1' => [-1]; + yield 'cache time equals -10' => [-10]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php index 3231617c126b9..87812731600ff 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php @@ -193,6 +193,37 @@ public function testSendWithOptionForEditMessage() $transport->send(new ChatMessage('testMessage', $options)); } + public function testSendWithOptionToAnswerCallbackQuery() + { + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->exactly(2)) + ->method('getStatusCode') + ->willReturn(200); + + $content = <<expects($this->once()) + ->method('getContent') + ->willReturn($content) + ; + + $client = new MockHttpClient(function (string $method, string $url) use ($response): ResponseInterface { + $this->assertStringEndsWith('/answerCallbackQuery', $url); + + return $response; + }); + + $transport = $this->createTransport($client, 'testChannel'); + $options = (new TelegramOptions())->answerCallbackQuery('123', true, 1); + + $transport->send(new ChatMessage('testMessage', $options)); + } + public function testSendWithChannelOverride() { $channelOverride = 'channelOverride'; From d097a63983c266f50558c01932ad7ab3e0156abb Mon Sep 17 00:00:00 2001 From: Quentin Dequippe Date: Thu, 3 Nov 2022 17:04:21 +0100 Subject: [PATCH 030/475] Add Mastodon Notifier --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 + .../Notifier/Bridge/Mastodon/.gitattributes | 4 + .../Notifier/Bridge/Mastodon/.gitignore | 3 + .../Notifier/Bridge/Mastodon/CHANGELOG.md | 7 + .../Notifier/Bridge/Mastodon/LICENSE | 19 +++ .../Bridge/Mastodon/MastodonOptions.php | 58 +++++++ .../Bridge/Mastodon/MastodonTransport.php | 152 ++++++++++++++++++ .../Mastodon/MastodonTransportFactory.php | 42 +++++ .../Notifier/Bridge/Mastodon/README.md | 23 +++ .../Tests/MastodonTransportFactoryTest.php | 65 ++++++++ .../Mastodon/Tests/MastodonTransportTest.php | 112 +++++++++++++ .../Bridge/Mastodon/Tests/fixtures.gif | Bin 0 -> 185 bytes .../Notifier/Bridge/Mastodon/composer.json | 33 ++++ .../Notifier/Bridge/Mastodon/phpunit.xml.dist | 31 ++++ .../Bridge/Twitter/TwitterOptions.php | 1 - .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 3 + src/Symfony/Component/Notifier/Transport.php | 2 + 19 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/fixtures.gif create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/Mastodon/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index b68c48dab4f2d..caf638ad588b7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -152,6 +152,7 @@ use Symfony\Component\Notifier\Bridge\LineNotify\LineNotifyTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory as MailjetNotifierTransportFactory; +use Symfony\Component\Notifier\Bridge\Mastodon\MastodonTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransport; @@ -2580,6 +2581,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ LineNotifyTransportFactory::class => 'notifier.transport_factory.line-notify', LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', MailjetNotifierTransportFactory::class => 'notifier.transport_factory.mailjet', + MastodonTransportFactory::class => 'notifier.transport_factory.mastodon', MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', MercureTransportFactory::class => 'notifier.transport_factory.mercure', MessageBirdTransport::class => 'notifier.transport_factory.message-bird', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 0c7e55d56ee5a..0d38a65d05163 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -37,6 +37,7 @@ use Symfony\Component\Notifier\Bridge\LineNotify F438 \LineNotifyTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; +use Symfony\Component\Notifier\Bridge\Mastodon\MastodonTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; @@ -321,5 +322,9 @@ ->set('notifier.transport_factory.line-notify', LineNotifyTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.mastodon', MastodonTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Mastodon/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/.gitignore b/src/Symfony/Component/Notifier/Bridge/Mastodon/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Mastodon/CHANGELOG.md new file mode 100644 index 0000000000000..1f2c8f86cde72 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE b/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE new file mode 100644 index 0000000000000..0ece8964f767d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonOptions.php b/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonOptions.php new file mode 100644 index 0000000000000..477cd2d0b0082 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonOptions.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Mastodon; + +use Symfony\Component\Mime\Part\File; +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +final class MastodonOptions implements MessageOptionsInterface +{ + public function __construct( + private array $options = [], + ) { + } + + public function toArray(): array + { + return $this->options; + } + + public function getRecipientId(): ?string + { + return null; + } + + /** + * @param string[] $choices + */ + public function poll(array $choices, int $expiresIn): self + { + $this->options['poll'] = [ + 'options' => $choices, + 'expires_in' => $expiresIn, + ]; + + return $this; + } + + public function attachMedia(File $file, File $thumbnail = null, string $description = null, string $focus = null): self + { + $this->options['attach'][] = [ + 'file' => $file, + 'thumbnail' => $thumbnail, + 'description' => $description, + 'focus' => $focus, + ]; + + return $this; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransport.php b/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransport.php new file mode 100644 index 0000000000000..57cc4fba9e0e4 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransport.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Mastodon; + +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\File; +use Symfony\Component\Mime\Part\Multipart\FormDataPart; +use Symfony\Component\Notifier\Exception\RuntimeException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Quentin Dequippe + * + * @see https://docs.joinmastodon.org + */ +final class MastodonTransport extends AbstractTransport +{ + public function __construct(#[\SensitiveParameter] private readonly string $accessToken, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('mastodon://%s', $this->getEndpoint()); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof ChatMessage && (null === $message->getOptions() || $message->getOptions() instanceof MastodonOptions); + } + + public function request(string $method, string $url, array $options): ResponseInterface + { + $url = sprintf('https://%s%s', $this->getEndpoint(), $url); + + $options['auth_bearer'] = $this->accessToken; + + return $this->client->request($method, $url, $options); + } + + /** + * @see https://docs.joinmastodon.org/methods/statuses/ + */ + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof ChatMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); + } + + $options = $message->getOptions()?->toArray() ?? []; + $options['status'] = $message->getSubject(); + $response = null; + + try { + if (isset($options['attach'])) { + $options['media_ids'] = $this->uploadMedia($options['attach']); + unset($options['attach']); + } + + $response = $this->request('POST', '/api/v1/statuses', ['json' => $options]); + $statusCode = $response->getStatusCode(); + $result = $response->toArray(false); + } catch (ExceptionInterface $e) { + if (null !== $response) { + throw new TransportException($e->getMessage(), $response, 0, $e); + } + + throw new RuntimeException($e->getMessage(), 0, $e); + } + + if (200 !== $statusCode) { + throw new TransportException(sprintf('Unable to post the Mastodon message: "%s" (%s).', $result['error_description'], $result['error']), $response); + } + + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($result['id']); + + return $sentMessage; + } + + /** + * @param array $media + */ + private function uploadMedia(array $media): array + { + $responses = []; + + foreach ($media as [ + 'file' => $file, + 'thumbnail' => $thumbnail, + 'description' => $description, + 'focus' => $focus, + ]) { + $formDataPart = new FormDataPart(array_filter([ + 'file' => new DataPart($file), + 'thumbnail' => $thumbnail ? new DataPart($thumbnail) : null, + 'description' => $description, + 'focus' => $focus, + ])); + + $headers = []; + foreach ($formDataPart->getPreparedHeaders()->all() as $header) { + $headers[] = $header->toString(); + } + + $responses[] = $this->request('POST', '/api/v2/media', [ + 'headers' => $headers, + 'body' => $formDataPart->bodyToIterable(), + ]); + } + + $mediaIds = []; + + try { + foreach ($responses as $i => $response) { + unset($responses[$i]); + $result = $response->toArray(false); + + if (300 <= $response->getStatusCode()) { + throw new TransportException(sprintf('Unable to upload media as attachment: "%s" (%s).', $result['error_description'], $result['error']), $response); + } + + $mediaIds[] = $result['id']; + } + } finally { + foreach ($responses as $response) { + $response->cancel(); + } + } + + return $mediaIds; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransportFactory.php new file mode 100644 index 0000000000000..df8afd9a31d88 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/MastodonTransportFactory.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Mastodon; + +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author Quentin Dequippe + */ +final class MastodonTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): MastodonTransport + { + $scheme = $dsn->getScheme(); + + if ('mastodon' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'mastodon', $this->getSupportedSchemes()); + } + + $token = $this->getUser($dsn); + $host = $dsn->getHost(); + $port = $dsn->getPort(); + + return (new MastodonTransport($token, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return ['mastodon']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/README.md b/src/Symfony/Component/Notifier/Bridge/Mastodon/README.md new file mode 100644 index 0000000000000..f8a680801628f --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/README.md @@ -0,0 +1,23 @@ +Mastodon Notifier +================= + +Provides Mastodon integration for Symfony Notifier. + +DSN example +----------- + +``` +MASTODON_DSN=mastodon://ACCESS_TOKEN@HOST +``` + +where: + - `ACCESS_TOKEN` is your Mastodon access token + - `HOST` is your Mastodon host + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportFactoryTest.php new file mode 100644 index 0000000000000..08f87f7650497 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportFactoryTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Mastodon\Tests; + +use Symfony\Component\Notifier\Bridge\Mastodon\MastodonTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +/** + * @author Quentin Dequippe + */ +class MastodonTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): MastodonTransportFactory + { + return new MastodonTransportFactory(); + } + + public function createProvider(): iterable + { + yield [ + 'mastodon://host.test', + 'mastodon://accessToken@host.test', + ]; + + yield [ + 'mastodon://example.com', + 'mastodon://accessToken@example.com', + ]; + + yield [ + 'mastodon://example.com', + 'mastodon://accessToken@example.com', + ]; + + yield [ + 'mastodon://example.com', + 'mastodon://accessToken@example.com', + ]; + } + + public function supportsProvider(): iterable + { + yield [true, 'mastodon://token@host']; + yield [false, 'somethingElse://token@host']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing token' => ['mastodon://host.test']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://token@host']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportTest.php new file mode 100644 index 0000000000000..17f4fb6fa4d0e --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/MastodonTransportTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Mastodon\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\Mime\Part\File; +use Symfony\Component\Notifier\Bridge\Mastodon\MastodonOptions; +use Symfony\Component\Notifier\Bridge\Mastodon\MastodonTransport; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Quentin Dequippe + */ +class MastodonTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null): MastodonTransport + { + return (new MastodonTransport('testAccessToken', $client ?? $this->createMock(HttpClientInterface::class)))->setHost('host.test'); + } + + public function toStringProvider(): iterable + { + yield ['mastodon://host.test', $this->createTransport()]; + } + + public function supportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello World!')]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello World!')]; + yield [$this->createMock(MessageInterface::class)]; + } + + public function testBasicStatus() + { + $transport = $this->createTransport(new MockHttpClient(function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://host.test/api/v1/statuses', $url); + $this->assertSame('{"status":"Hello World!"}', $options['body']); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"id":"103254962155278888"}'); + })); + + $result = $transport->send(new ChatMessage('Hello World!')); + + $this->assertSame('103254962155278888', $result->getMessageId()); + } + + public function testStatusWithPoll() + { + $transport = $this->createTransport(new MockHttpClient(function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://host.test/api/v1/statuses', $url); + $this->assertSame('{"poll":{"options":["choice1","choice2"],"expires_in":3600},"status":"Hello World!"}', $options['body']); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"id":"103254962155278888"}'); + })); + + $options = (new MastodonOptions()) + ->poll(['choice1', 'choice2'], 3600); + $result = $transport->send(new ChatMessage('Hello World!', $options)); + + $this->assertSame('103254962155278888', $result->getMessageId()); + } + + public function testStatusWithMedia() + { + $transport = $this->createTransport(new MockHttpClient((function () { + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://host.test/api/v2/media', $url); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"id":"256"}'); + }; + + yield function (string $method, string $url, array $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://host.test/api/v1/statuses', $url); + $this->assertSame('{"status":"Hello World!","media_ids":["256"]}', $options['body']); + $this->assertArrayHasKey('authorization', $options['normalized_headers']); + + return new MockResponse('{"id":"103254962155278888"}'); + }; + })())); + + $options = (new MastodonOptions()) + ->attachMedia(new File(__DIR__.'/fixtures.gif'), new File(__DIR__.'/fixtures.gif'), 'A fixture', '1.0,0.75'); + $result = $transport->send(new ChatMessage('Hello World!', $options)); + + $this->assertSame('103254962155278888', $result->getMessageId()); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/fixtures.gif b/src/Symfony/Component/Notifier/Bridge/Mastodon/Tests/fixtures.gif new file mode 100644 index 0000000000000000000000000000000000000000..443aca422f7624b271903e5fbb577c7f99786c0e GIT binary patch literal 185 zcmZ?wbhEHb0d66k_=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.2" + }, + "require-dev": { + "symfony/mime": "^6.2" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Mastodon\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Mastodon/phpunit.xml.dist new file mode 100644 index 0000000000000..096fbc3c43f9d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Resources + ./Tests + ./vendor + + + diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterOptions.php b/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterOptions.php index 6037cb22660e5..2fc331e1c8491 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/TwitterOptions.php @@ -22,7 +22,6 @@ final class TwitterOptions implements MessageOptionsInterface public const REPLY_MENTIONED_USERS = 'mentionedUsers'; public const REPLY_FOLLOWING = 'following'; - public function __construct( private array $options = [], ) { diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index 28e7d971b1cd6..bbbd56a4cd093 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -112,6 +112,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Mailjet\MailjetTransportFactory::class, 'package' => 'symfony/mailjet-notifier', ], + 'mastodon' => [ + 'class' => Bridge\Mastodon\MastodonTransportFactory::class, + 'package' => 'symfony/mastodon-notifier', + ], 'mattermost' => [ 'class' => Bridge\Mattermost\MattermostTransportFactory::class, 'package' => 'symfony/mattermost-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index f97a2ca25a833..8dd5d8d92c1a7 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -35,6 +35,7 @@ use Symfony\Component\Notifier\Bridge\LineNotify\LineNotifyTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; +use Symfony\Component\Notifier\Bridge\Mastodon\MastodonTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; @@ -101,6 +102,7 @@ public static function setUpBeforeClass(): void LineNotifyTransportFactory::class => false, LinkedInTransportFactory::class => false, MailjetTransportFactory::class => false, + MastodonTransportFactory::class => false, MattermostTransportFactory::class => false, MercureTransportFactory::class => false, MessageBirdTransportFactory::class => false, @@ -172,6 +174,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat yield ['linenotify', 'symfony/line-notify-notifier']; yield ['linkedin', 'symfony/linked-in-notifier']; yield ['mailjet', 'symfony/mailjet-notifier']; + yield ['mastodon', 'symfony/mastodon-notifier']; yield ['mattermost', 'symfony/mattermost-notifier']; yield ['mercure', 'symfony/mercure-notifier']; yield ['messagebird', 'symfony/message-bird-notifier']; diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index 3d2bbe9075e4f..78ab723de4ab0 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -31,6 +31,7 @@ use Symfony\Component\Notifier\Bridge\Isendpro\IsendproTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; +use Symfony\Component\Notifier\Bridge\Mastodon\MastodonTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; use Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; @@ -98,6 +99,7 @@ final class Transport IsendproTransportFactory::class, LightSmsTransportFactory::class, MailjetTransportFactory::class, + MastodonTransportFactory::class, MattermostTransportFactory::class, MessageBirdTransportFactory::class, MessageMediaTransportFactory::class, From bde17b7f519ba9166585fc1d428582375ffbce20 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 2 Dec 2022 22:43:20 +0100 Subject: [PATCH 031/475] [VarDumper] Add caster for WeakMap --- src/Symfony/Component/VarDumper/CHANGELOG.md | 5 +++++ .../Component/VarDumper/Caster/SplCaster.php | 22 +++++++++++++++++-- .../VarDumper/Cloner/AbstractCloner.php | 1 + .../VarDumper/Tests/Caster/SplCasterTest.php | 20 +++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/VarDumper/CHANGELOG.md b/src/Symfony/Component/VarDumper/CHANGELOG.md index ffa4daf2b5a91..97204fc67d042 100644 --- a/src/Symfony/Component/VarDumper/CHANGELOG.md +++ b/src/Symfony/Component/VarDumper/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add caster for `WeakMap` + 6.2 --- diff --git a/src/Symfony/Component/VarDumper/Caster/SplCaster.php b/src/Symfony/Component/VarDumper/Caster/SplCaster.php index 448afbad90a47..72ac5dce9d38c 100644 --- a/src/Symfony/Component/VarDumper/Caster/SplCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/SplCaster.php @@ -184,10 +184,10 @@ public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $s $clone = clone $c; foreach ($clone as $obj) { - $storage[] = [ + $storage[] = new EnumStub([ 'object' => $obj, 'info' => $clone->getInfo(), - ]; + ]); } $a += [ @@ -211,6 +211,24 @@ public static function castWeakReference(\WeakReference $c, array $a, Stub $stub return $a; } + public static function castWeakMap(\WeakMap $c, array $a, Stub $stub, bool $isNested) + { + $map = []; + + foreach (clone $c as $obj => $data) { + $map[] = new EnumStub([ + 'object' => $obj, + 'data' => $data, + ]); + } + + $a += [ + Caster::PREFIX_VIRTUAL.'map' => $map, + ]; + + return $a; + } + private static function castSplArray(\ArrayObject|\ArrayIterator $c, array $a, Stub $stub, bool $isNested): array { $prefix = Caster::PREFIX_VIRTUAL; diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index c722a8e905c2b..3b92fad0f3d3c 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -124,6 +124,7 @@ abstract class AbstractCloner implements ClonerInterface 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'], 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'], + 'WeakMap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakMap'], 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/SplCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/SplCasterTest.php index 26887b1362765..ed93bfc4afb4b 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/SplCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/SplCasterTest.php @@ -213,6 +213,26 @@ public function testBadSplFileInfo() EOTXT; $this->assertDumpEquals($expected, $var); } + + public function testWeakMap() + { + $var = new \WeakMap(); + $obj = new \stdClass(); + $var[$obj] = 123; + + $expected = << { + object: {} + data: 123 + } + ] + } + EOTXT; + + $this->assertDumpEquals($expected, $var); + } } class MyArrayIterator extends \ArrayIterator From 9a878526d295b5400cc3a20c263483870632c627 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Mon, 5 Dec 2022 09:57:34 +0100 Subject: [PATCH 032/475] [Notifier][Line] Fix tests --- .../LineNotify/Tests/LineNotifyTransportTest.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php b/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php index d315e1b377533..b7b83aac7305d 100644 --- a/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/Tests/LineNotifyTransportTest.php @@ -13,7 +13,6 @@ use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Notifier\Bridge\LineNotify\LineNotifyTransport; -use Symfony\Component\Notifier\Exception\LengthException; use Symfony\Component\Notifier\Exception\TransportException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; @@ -48,16 +47,6 @@ public function unsupportedMessagesProvider(): iterable yield [$this->createMock(MessageInterface::class)]; } - public function testSendChatMessageWithMoreThan2000CharsThrowsLogicException() - { - $transport = $this->createTransport(); - - $this->expectException(LengthException::class); - $this->expectExceptionMessage('The subject length of a Line message must not exceed 1000 characters.'); - - $transport->send(new ChatMessage(str_repeat('囍', 1001))); - } - public function testSendWithErrorResponseThrows() { $response = $this->createMock(ResponseInterface::class); @@ -66,7 +55,7 @@ public function testSendWithErrorResponseThrows() ->willReturn(400); $response->expects($this->once()) ->method('getContent') - ->willReturn(json_encode(['message' => 'testDescription', 'code' => 'testErrorCode'])); + ->willReturn(json_encode(['message' => 'testDescription', 'code' => 'testErrorCode', 'status' => 'testStatus'])); $client = new MockHttpClient(static function () use ($response): ResponseInterface { return $response; @@ -75,7 +64,7 @@ public function testSendWithErrorResponseThrows() $transport = $this->createTransport($client); $this->expectException(TransportException::class); - $this->expectExceptionMessageMatches('/testDescription.+testErrorCode/'); + $this->expectExceptionMessageMatches('/testMessage.+testDescription/'); $transport->send(new ChatMessage('testMessage')); } From c75dbca8f0d0b24f5343f79b480d2bb62041df88 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sat, 24 Sep 2022 19:18:41 +0200 Subject: [PATCH 033/475] [DependencyInjection][HttpKernel] Introduce build parameters --- UPGRADE-6.3.md | 10 ++++ .../DependencyInjection/CHANGELOG.md | 7 +++ .../Compiler/PassConfig.php | 14 +++-- .../Compiler/RemoveBuildParametersPass.php | 45 ++++++++++++++++ .../DependencyInjection/Dumper/PhpDumper.php | 30 +++++++++-- .../Exception/ParameterNotFoundException.php | 2 + .../RemoveBuildParametersPassTest.php | 33 ++++++++++++ .../Tests/Dumper/PhpDumperTest.php | 25 ++++----- .../php/services9_inlined_factories.txt | 2 - .../php/services9_lazy_inlined_factories.txt | 2 - .../Fixtures/php/services_inline_requires.php | 52 ------------------- .../Tests/ParameterBag/ParameterBagTest.php | 1 + src/Symfony/Component/HttpKernel/CHANGELOG.md | 5 ++ src/Symfony/Component/HttpKernel/Kernel.php | 26 ++++++++++ 14 files changed, 173 insertions(+), 81 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/RemoveBuildParametersPass.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveBuildParametersPassTest.php diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index e1655bfa81b62..17e9002f0882e 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -1,6 +1,16 @@ UPGRADE FROM 6.2 to 6.3 ======================= +DependencyInjection +------------------- + + * Deprecate `PhpDumper` options `inline_factories_parameter` and `inline_class_loader_parameter`, use `inline_factories` and `inline_class_loader` instead + +HttpKernel +---------- + + * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead + SecurityBundle -------------- diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index b3626d02cc107..0282cd57f2823 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +6.3 +--- + + * Add options `inline_factories` and `inline_class_loader` to `PhpDumper::dump()` + * Deprecate `PhpDumper` options `inline_factories_parameter` and `inline_class_loader_parameter` + * Add `RemoveBuildParametersPass`, which removes parameters starting with a dot during compilation + 6.2 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 3acbe26de0d3c..624a97db8ae0a 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -90,11 +90,15 @@ public function __construct() new DefinitionErrorExceptionPass(), ]]; - $this->afterRemovingPasses = [[ - new ResolveHotPathPass(), - new ResolveNoPreloadPass(), - new AliasDeprecatedPublicServicesPass(), - ]]; + $this->afterRemovingPasses = [ + 0 => [ + new ResolveHotPathPass(), + new ResolveNoPreloadPass(), + new AliasDeprecatedPublicServicesPass(), + ], + // Let build parameters be available as late as possible + -2048 => [new RemoveBuildParametersPass()], + ]; } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RemoveBuildParametersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RemoveBuildParametersPass.php new file mode 100644 index 0000000000000..44d9fa8c64225 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/RemoveBuildParametersPass.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class RemoveBuildParametersPass implements CompilerPassInterface +{ + /** + * @var array + */ + private array $removedParameters = []; + + public function process(ContainerBuilder $container) + { + $parameterBag = $container->getParameterBag(); + $this->removedParameters = []; + + foreach ($parameterBag->all() as $name => $value) { + if ('.' === ($name[0] ?? '')) { + $this->removedParameters[$name] = $value; + + $parameterBag->remove($name); + $container->log($this, sprintf('Removing build parameter "%s".', $name)); + } + } + } + + /** + * @return array + */ + public function getRemovedParameters(): array + { + return $this->removedParameters; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 4d827d53e28b7..0132446bda957 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -137,8 +137,10 @@ public function dump(array $options = []): string|array 'debug' => true, 'hot_path_tag' => 'container.hot_path', 'preload_tags' => ['container.preload', 'container.no_preload'], - 'inline_factories_parameter' => 'container.dumper.inline_factories', - 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader', + 'inline_factories_parameter' => 'container.dumper.inline_factories', // @deprecated since Symfony 6.3 + 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader', // @deprecated since Symfony 6.3 + 'inline_factories' => null, + 'inline_class_loader' => null, 'preload_classes' => [], 'service_locator_tag' => 'container.service_locator', 'build_time' => time(), @@ -149,8 +151,28 @@ public function dump(array $options = []): string|array $this->asFiles = $options['as_files']; $this->hotPathTag = $options['hot_path_tag']; $this->preloadTags = $options['preload_tags']; - $this->inlineFactories = $this->asFiles && $options['inline_factories_parameter'] && $this->container->hasParameter($options['inline_factories_parameter']) && $this->container->getParameter($options['inline_factories_parameter']); - $this->inlineRequires = $options['inline_class_loader_parameter'] && ($this->container->hasParameter($options['inline_class_loader_parameter']) ? $this->container->getParameter($options['inline_class_loader_parameter']) : $options['debug']); + + $this->inlineFactories = false; + if (isset($options['inline_factories'])) { + $this->inlineFactories = $this->asFiles && $options['inline_factories']; + } elseif (!$options['inline_factories_parameter']) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Option "inline_factories_parameter" passed to "%s()" is deprecated, use option "inline_factories" instead.', __METHOD__); + } elseif ($this->container->hasParameter($options['inline_factories_parameter'])) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Option "inline_factories_parameter" passed to "%s()" is deprecated, use option "inline_factories" instead.', __METHOD__); + $this->inlineFactories = $this->asFiles && $this->container->getParameter($options['inline_factories_parameter']); + } + + $this->inlineRequires = $options['debug']; + if (isset($options['inline_class_loader'])) { + $this->inlineRequires = $options['inline_class_loader']; + } elseif (!$options['inline_class_loader_parameter']) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Option "inline_class_loader_parameter" passed to "%s()" is deprecated, use option "inline_class_loader" instead.', __METHOD__); + $this->inlineRequires = false; + } elseif ($this->container->hasParameter($options['inline_class_loader_parameter'])) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Option "inline_class_loader_parameter" passed to "%s()" is deprecated, use option "inline_class_loader" instead.', __METHOD__); + $this->inlineRequires = $this->container->getParameter($options['inline_class_loader_parameter']); + } + $this->serviceLocatorTag = $options['service_locator_tag']; $this->class = $options['class']; diff --git a/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php b/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php index 71c36f6866411..e8f593187e9a7 100644 --- a/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php +++ b/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php @@ -53,6 +53,8 @@ public function updateRepr() $this->message = sprintf('The service "%s" has a dependency on a non-existent parameter "%s".', $this->sourceId, $this->key); } elseif (null !== $this->sourceKey) { $this->message = sprintf('The parameter "%s" has a dependency on a non-existent parameter "%s".', $this->sourceKey, $this->key); + } elseif ('.' === ($this->key[0] ?? '')) { + $this->message = sprintf('Parameter "%s" not found. It was probably deleted during the compilation of the container.', $this->key); } else { $this->message = sprintf('You have requested a non-existent parameter "%s".', $this->key); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveBuildParametersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveBuildParametersPassTest.php new file mode 100644 index 0000000000000..4bc3b78ec0b1f --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveBuildParametersPassTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\RemoveBuildParametersPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class RemoveBuildParametersPassTest extends TestCase +{ + public function testBuildParametersShouldBeRemoved() + { + $builder = new ContainerBuilder(); + $builder->setParameter('foo', 'Foo'); + $builder->setParameter('.bar', 'Bar'); + + $pass = new RemoveBuildParametersPass(); + $pass->process($builder); + + $this->assertSame('Foo', $builder->getParameter('foo'), '"foo" parameter must be defined.'); + $this->assertFalse($builder->hasParameter('.bar'), '".bar" parameter must be removed.'); + $this->assertSame(['.bar' => 'Bar'], $pass->getRemovedParameters(), '".baz" parameter must be returned with its value.'); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 12d5b80f8baf3..816787154a364 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -222,7 +222,7 @@ public function testDumpAsFiles() ->addError('No-no-no-no'); $container->compile(); $dumper = new PhpDumper($container); - $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'inline_factories_parameter' => false, 'inline_class_loader_parameter' => false]), true); + $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'inline_factories' => false, 'inline_class_loader' => false]), true); if ('\\' === \DIRECTORY_SEPARATOR) { $dump = str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump); } @@ -241,7 +241,7 @@ public function testDumpAsFilesWithTypedReference() ->setPublic(true); $container->compile(); $dumper = new PhpDumper($container); - $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'inline_factories_parameter' => false, 'inline_class_loader_parameter' => false]), true); + $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'inline_factories' => false, 'inline_class_loader' => false]), true); if ('\\' === \DIRECTORY_SEPARATOR) { $dump = str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump); } @@ -252,8 +252,6 @@ public function testDumpAsFilesWithTypedReference() public function testDumpAsFilesWithFactoriesInlined() { $container = include self::$fixturesPath.'/containers/container9.php'; - $container->setParameter('container.dumper.inline_factories', true); - $container->setParameter('container.dumper.inline_class_loader', true); $container->getDefinition('bar')->addTag('hot'); $container->register('non_shared_foo', \Bar\FooClass::class) @@ -268,7 +266,7 @@ public function testDumpAsFilesWithFactoriesInlined() $container->compile(); $dumper = new PhpDumper($container); - $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'build_time' => 1563381341]), true); + $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'build_time' => 1563381341, 'inline_factories' => true, 'inline_class_loader' => true]), true); if ('\\' === \DIRECTORY_SEPARATOR) { $dump = str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump); @@ -279,8 +277,6 @@ public function testDumpAsFilesWithFactoriesInlined() public function testDumpAsFilesWithLazyFactoriesInlined() { $container = new ContainerBuilder(); - $container->setParameter('container.dumper.inline_factories', true); - $container->setParameter('container.dumper.inline_class_loader', true); $container->setParameter('lazy_foo_class', \Bar\FooClass::class); $container->register('lazy_foo', '%lazy_foo_class%') @@ -292,7 +288,7 @@ public function testDumpAsFilesWithLazyFactoriesInlined() $container->compile(); $dumper = new PhpDumper($container); - $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'build_time' => 1563381341]), true); + $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'build_time' => 1563381341, 'inline_factories' => true, 'inline_class_loader' => true]), true); if ('\\' === \DIRECTORY_SEPARATOR) { $dump = str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump); @@ -310,7 +306,7 @@ public function testNonSharedLazyDumpAsFiles() ->setLazy(true); $container->compile(); $dumper = new PhpDumper($container); - $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'inline_factories_parameter' => false, 'inline_class_loader_parameter' => false]), true); + $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'inline_factories' => false, 'inline_class_loader' => false]), true); if ('\\' === \DIRECTORY_SEPARATOR) { $dump = str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump); @@ -473,7 +469,7 @@ public function testEnvParameter() $container->compile(); $dumper = new PhpDumper($container); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/services26.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_EnvParameters', 'file' => self::$fixturesPath.'/php/services26.php', 'inline_factories_parameter' => false, 'inline_class_loader_parameter' => false])); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services26.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_EnvParameters', 'file' => self::$fixturesPath.'/php/services26.php', 'inline_factories' => false, 'inline_class_loader' => false])); require self::$fixturesPath.'/php/services26.php'; $container = new \Symfony_DI_PhpDumper_Test_EnvParameters(); @@ -994,7 +990,7 @@ public function testArrayParameters() $dumper = new PhpDumper($container); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_array_params.php', str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dumper->dump(['file' => self::$fixturesPath.'/php/services_array_params.php', 'inline_factories_parameter' => false, 'inline_class_loader_parameter' => false]))); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_array_params.php', str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dumper->dump(['file' => self::$fixturesPath.'/php/services_array_params.php', 'inline_factories' => false, 'inline_class_loader' => false]))); } public function testExpressionReferencingPrivateService() @@ -1162,11 +1158,10 @@ public function testInlineSelfRef() public function testHotPathOptimizations() { $container = include self::$fixturesPath.'/containers/container_inline_requires.php'; - $container->setParameter('inline_requires', true); $container->compile(); $dumper = new PhpDumper($container); - $dump = $dumper->dump(['hot_path_tag' => 'container.hot_path', 'inline_class_loader_parameter' => 'inline_requires', 'file' => self::$fixturesPath.'/php/services_inline_requires.php']); + $dump = $dumper->dump(['hot_path_tag' => 'container.hot_path', 'inline_class_loader' => true, 'file' => self::$fixturesPath.'/php/services_inline_requires.php']); if ('\\' === \DIRECTORY_SEPARATOR) { $dump = str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump); } @@ -1199,13 +1194,12 @@ public function testDumpHandlesObjectClassNames() new Reference('foo'), ]))->setPublic(true); - $container->setParameter('inline_requires', true); $container->compile(); $dumper = new PhpDumper($container); eval('?>'.$dumper->dump([ 'class' => 'Symfony_DI_PhpDumper_Test_Object_Class_Name', - 'inline_class_loader_parameter' => 'inline_requires', + 'inline_class_loader' => true, ])); $container = new \Symfony_DI_PhpDumper_Test_Object_Class_Name(); @@ -1281,7 +1275,6 @@ public function testUninitializedSyntheticReference() $dumper = new PhpDumper($container); eval('?>'.$dumper->dump([ 'class' => 'Symfony_DI_PhpDumper_Test_UninitializedSyntheticReference', - 'inline_class_loader_parameter' => 'inline_requires', ])); $container = new \Symfony_DI_PhpDumper_Test_UninitializedSyntheticReference(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt index 230bb357db000..137506abdf4b0 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt @@ -557,8 +557,6 @@ class ProjectServiceContainer extends Container 'baz_class' => 'BazClass', 'foo_class' => 'Bar\\FooClass', 'foo' => 'bar', - 'container.dumper.inline_factories' => true, - 'container.dumper.inline_class_loader' => true, ]; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt index 7af59984549ae..aa8c5da006f30 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt @@ -143,8 +143,6 @@ class ProjectServiceContainer extends Container protected function getDefaultParameters(): array { return [ - 'container.dumper.inline_factories' => true, - 'container.dumper.inline_class_loader' => true, 'lazy_foo_class' => 'Bar\\FooClass', ]; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php index 386e5a5d0d739..d0509ad594369 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php @@ -18,8 +18,6 @@ class ProjectServiceContainer extends Container public function __construct() { - $this->parameters = $this->getDefaultParameters(); - $this->services = $this->privates = []; $this->methodMap = [ 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\ParentNotExists' => 'getParentNotExistsService', @@ -86,54 +84,4 @@ protected function getC2Service() return $this->services['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2(new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3()); } - - public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null - { - if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { - throw new ParameterNotFoundException($name); - } - if (isset($this->loadedDynamicParameters[$name])) { - return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); - } - - return $this->parameters[$name]; - } - - public function hasParameter(string $name): bool - { - return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters); - } - - public function setParameter(string $name, $value): void - { - throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); - } - - public function getParameterBag(): ParameterBagInterface - { - if (null === $this->parameterBag) { - $parameters = $this->parameters; - foreach ($this->loadedDynamicParameters as $name => $loaded) { - $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); - } - $this->parameterBag = new FrozenParameterBag($parameters); - } - - return $this->parameterBag; - } - - private $loadedDynamicParameters = []; - private $dynamicParameters = []; - - private function getDynamicParameter(string $name) - { - throw new ParameterNotFoundException($name); - } - - protected function getDefaultParameters(): array - { - return [ - 'inline_requires' => true, - ]; - } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/ParameterBagTest.php b/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/ParameterBagTest.php index 7539da5d2ae6b..e97ec063e52a8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/ParameterBagTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/ParameterBagTest.php @@ -121,6 +121,7 @@ public function provideGetThrowParameterNotFoundExceptionData() ['', 'You have requested a non-existent parameter "".'], ['fiz.bar.boo', 'You have requested a non-existent parameter "fiz.bar.boo". You cannot access nested array items, do you want to inject "fiz" instead?'], + ['.foo', 'Parameter ".foo" not found. It was probably deleted during the compilation of the container. Did you mean this: "foo"?'], ]; } diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 2a5aad623bfeb..3122668e697cb 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead + 6.2 --- diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 20a47dce86719..96bb3d6a9627c 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -17,6 +17,7 @@ use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\RemoveBuildParametersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; @@ -648,12 +649,37 @@ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container // cache the container $dumper = new PhpDumper($container); + $buildParameters = []; + foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { + if ($pass instanceof RemoveBuildParametersPass) { + $buildParameters = array_merge($buildParameters, $pass->getRemovedParameters()); + } + } + + $inlineFactories = false; + if (isset($buildParameters['.container.dumper.inline_factories'])) { + $inlineFactories = $buildParameters['.container.dumper.inline_factories']; + } elseif ($container->hasParameter('container.dumper.inline_factories')) { + trigger_deprecation('symfony/http-kernel', '6.3', 'Parameter "%s" is deprecated, use ".%$1s" instead.', 'container.dumper.inline_factories'); + $inlineFactories = $container->getParameter('container.dumper.inline_factories'); + } + + $inlineClassLoader = $this->debug; + if (isset($buildParameters['.container.dumper.inline_class_loader'])) { + $inlineClassLoader = $buildParameters['.container.dumper.inline_class_loader']; + } elseif ($container->hasParameter('container.dumper.inline_class_loader')) { + trigger_deprecation('symfony/http-kernel', '6.3', 'Parameter "%s" is deprecated, use ".%$1s" instead.', 'container.dumper.inline_class_loader'); + $inlineClassLoader = $container->getParameter('container.dumper.inline_class_loader'); + } + $content = $dumper->dump([ 'class' => $class, 'base_class' 10000 => $baseClass, 'file' => $cache->getPath(), 'as_files' => true, 'debug' => $this->debug, + 'inline_factories' => $inlineFactories, + 'inline_class_loader' => $inlineClassLoader, 'build_time' => $container->hasParameter('kernel.container_build_time') ? $container->getParameter('kernel.container_build_time') : time(), 'preload_classes' => array_map('get_class', $this->bundles), ]); From a9f1250c1e40664e59d9f7ee76aa4e7e9783e52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 5 Dec 2022 16:50:37 +0100 Subject: [PATCH 034/475] [Console] Do no preprend empty line if the buffer is empty --- src/Symfony/Component/Console/Style/SymfonyStyle.php | 2 +- .../Style/SymfonyStyle/command/command_23.php | 11 +++++++++++ .../Fixtures/Style/SymfonyStyle/output/output_23.txt | 1 + .../Fixtures/Style/SymfonyStyle/output/output_6.txt | 1 - 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_23.php create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_23.txt diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php index 997f86279d918..4dde785e86420 100644 --- a/src/Symfony/Component/Console/Style/SymfonyStyle.php +++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php @@ -375,7 +375,7 @@ private function autoPrependText(): void { $fetched = $this->bufferedOutput->fetch(); // Prepend new line if last char isn't EOL: - if (!str_ends_with($fetched, "\n")) { + if ($fetched && !str_ends_with($fetched, "\n")) { $this->newLine(); } } diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_23.php b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_23.php new file mode 100644 index 0000000000000..ab32dcaf3e7bf --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_23.php @@ -0,0 +1,11 @@ +text('Hello'); +}; diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_23.txt b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_23.txt new file mode 100644 index 0000000000000..63105f17b862b --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_23.txt @@ -0,0 +1 @@ + Hello diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt index 5f2d33c148a9e..7f1d478a325f7 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt @@ -1,4 +1,3 @@ - * Lorem ipsum dolor sit amet * consectetur adipiscing elit From 1e40f0faedcb30b01c0dfd1ad8bffc054d914345 Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Mon, 5 Dec 2022 13:22:50 -0400 Subject: [PATCH 035/475] [Notifier] Fix Termii bridge exception test --- .../Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 0af7287f60b6a..fa7c299d42674 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -56,6 +56,7 @@ use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; +use Symfony\Component\Notifier\Bridge\Termii\TermiiTransportFactory; use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Twitter\TwitterTransportFactory; @@ -118,6 +119,7 @@ public static function setUpBeforeClass(): void SpotHitTransportFactory::class => false, TelegramTransportFactory::class => false, TelnyxTransportFactory::class => false, + TermiiTransportFactory::class => false, TurboSmsTransportFactory::class => false, TwilioTransportFactory::class => false, TwitterTransportFactory::class => false, From 795afb1f45286fffdda2ab741808f0c08ed70d22 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Mon, 5 Dec 2022 20:27:29 +0100 Subject: [PATCH 036/475] [DependencyInjection] Fix typo in RemoveBuildParametersPassTest --- .../Tests/Compiler/RemoveBuildParametersPassTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveBuildParametersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveBuildParametersPassTest.php index 4bc3b78ec0b1f..4982f626968ca 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveBuildParametersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveBuildParametersPassTest.php @@ -28,6 +28,6 @@ public function testBuildParametersShouldBeRemoved() $this->assertSame('Foo', $builder->getParameter('foo'), '"foo" parameter must be defined.'); $this->assertFalse($builder->hasParameter('.bar'), '".bar" parameter must be removed.'); - $this->assertSame(['.bar' => 'Bar'], $pass->getRemovedParameters(), '".baz" parameter must be returned with its value.'); + $this->assertSame(['.bar' => 'Bar'], $pass->getRemovedParameters(), '".bar" parameter must be returned with its value.'); } } From b65b78b8c5576a6a1660d3e0b1cbccee271dc334 Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Mon, 5 Dec 2022 13:26:08 -0400 Subject: [PATCH 037/475] [Notifier] Fix RingCentral bridge exception test --- .../Tests/Exception/UnsupportedSchemeExceptionTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 8dd5d8d92c1a7..b3ea68a180aa5 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -45,7 +45,10 @@ use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +<<<<<<< HEAD use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransportFactory; +======= +>>>>>>> d8597449e5 ([Notifier] Fix RingCentral bridge exception test) use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; From be77a1f4873650888ad9b36028ea3e9670e6097a Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:57:06 -0400 Subject: [PATCH 038/475] [Notifier] Add options to SmsMessage --- .../Component/Notifier/Message/SmsMessage.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Notifier/Message/SmsMessage.php b/src/Symfony/Component/Notifier/Message/SmsMessage.php index b4369b4236565..7a0ef9ac6d096 100644 --- a/src/Symfony/Component/Notifier/Message/SmsMessage.php +++ b/src/Symfony/Component/Notifier/Message/SmsMessage.php @@ -24,8 +24,9 @@ class SmsMessage implements MessageInterface private string $subject; private string $phone; private string $from; + private ?MessageOptionsInterface $options; - public function __construct(string $phone, string $subject, string $from = '') + public function __construct(string $phone, string $subject, string $from = '', MessageOptionsInterface $options = null) { if ('' === $phone) { throw new InvalidArgumentException(sprintf('"%s" needs a phone number, it cannot be empty.', __CLASS__)); @@ -34,6 +35,7 @@ public function __construct(string $phone, string $subject, string $from = '') $this->subject = $subject; $this->phone = $phone; $this->from = $from; + $this->options = $options; } public static function fromNotification(Notification $notification, SmsRecipientInterface $recipient): self @@ -110,8 +112,18 @@ public function getFrom(): string return $this->from; } + /** + * @return $this + */ + public function options(MessageOptionsInterface $options): static + { + $this->options = $options; + + return $this; + } + public function getOptions(): ?MessageOptionsInterface { - return null; + return $this->options; } } From 52b6fa74162e02db0c33387e4cc8187d5fb8208f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 5 Dec 2022 15:38:26 +0100 Subject: [PATCH 039/475] [ProxyManagerBridge] Deprecate the package --- .../Tests/LegacyManagerRegistryTest.php | 136 ++++++++++++++++++ .../Doctrine/Tests/ManagerRegistryTest.php | 63 ++++---- .../Doctrine/Tests/TestManagerRegistry.php | 27 ++++ src/Symfony/Bridge/Doctrine/composer.json | 5 +- src/Symfony/Bridge/ProxyManager/CHANGELOG.md | 5 + .../Instantiator/RuntimeInstantiator.php | 4 + .../LazyProxy/PhpDumper/ProxyDumper.php | 4 + .../Tests/LazyProxy/ContainerBuilderTest.php | 2 + .../Tests/LazyProxy/Dumper/PhpDumperTest.php | 2 + .../Instantiator/RuntimeInstantiatorTest.php | 2 + .../LazyProxy/PhpDumper/ProxyDumperTest.php | 2 + src/Symfony/Bridge/ProxyManager/composer.json | 3 +- 12 files changed, 213 insertions(+), 42 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/Tests/LegacyManagerRegistryTest.php create mode 100644 src/Symfony/Bridge/Doctrine/Tests/TestManagerRegistry.php diff --git a/src/Symfony/Bridge/Doctrine/Tests/LegacyManagerRegistryTest.php b/src/Symfony/Bridge/Doctrine/Tests/LegacyManagerRegistryTest.php new file mode 100644 index 0000000000000..2989d0b61f228 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/LegacyManagerRegistryTest.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests; + +use PHPUnit\Framework\TestCase; +use ProxyManager\Proxy\LazyLoadingInterface; +use ProxyManager\Proxy\ValueHolderInterface; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +use Symfony\Bridge\ProxyManager\Tests\LazyProxy\Dumper\PhpDumperTest; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\Filesystem\Filesystem; + +/** + * @group legacy + */ +class LegacyManagerRegistryTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + $test = new PhpDumperTest(); + $test->testDumpContainerWithProxyServiceWillShareProxies(); + } + + public function testResetService() + { + $container = new \LazyServiceProjectServiceContainer(); + + $registry = new TestManagerRegistry('name', [], ['defaultManager' => 'foo'], 'defaultConnection', 'defaultManager', 'proxyInterfaceName'); + $registry->setTestContainer($container); + + $foo = $container->get('foo'); + $foo->bar = 123; + $this->assertTrue(isset($foo->bar)); + + $registry->resetManager(); + + $this->assertSame($foo, $container->get('foo')); + $this->assertObjectNotHasAttribute('bar', $foo); + } + + /** + * When performing an entity manager lazy service reset, the reset operations may re-use the container + * to create a "fresh" service: when doing so, it can happen that the "fresh" service is itself a proxy. + * + * Because of that, the proxy will be populated with a wrapped value that is itself a proxy: repeating + * the reset operation keeps increasing this nesting until the application eventually runs into stack + * overflow or memory overflow operations, which can happen for long-running processes that rely on + * services that are reset very often. + */ + public function testResetServiceWillNotNestFurtherLazyServicesWithinEachOther() + { + // This test scenario only applies to containers composed as a set of generated sources + $this->dumpLazyServiceProjectAsFilesServiceContainer(); + + /** @var ContainerInterface $container */ + $container = new \LazyServiceProjectAsFilesServiceContainer(); + + $registry = new TestManagerRegistry( + 'irrelevant', + [], + ['defaultManager' => 'foo'], + 'irrelevant', + 'defaultManager', + 'irrelevant' + ); + $registry->setTestContainer($container); + + $service = $container->get('foo'); + + self::assertInstanceOf(\stdClass::class, $service); + self::assertInstanceOf(LazyLoadingInterface::class, $service); + self::assertInstanceOf(ValueHolderInterface::class, $service); + self::assertFalse($service->isProxyInitialized()); + + $service->initializeProxy(); + + self::assertTrue($container->initialized('foo')); + self::assertTrue($service->isProxyInitialized()); + + $registry->resetManager(); + $service->initializeProxy(); + + $wrappedValue = $service->getWrappedValueHolderValue(); + self::assertInstanceOf(\stdClass::class, $wrappedValue); + self::assertNotInstanceOf(LazyLoadingInterface::class, $wrappedValue); + self::assertNotInstanceOf(ValueHolderInterface::class, $wrappedValue); + } + + private function dumpLazyServiceProjectAsFilesServiceContainer() + { + if (class_exists(\LazyServiceProjectAsFilesServiceContainer::class, false)) { + return; + } + + $container = new ContainerBuilder(); + + $container->register('foo', \stdClass::class) + ->setPublic(true) + ->setLazy(true); + $container->compile(); + + $fileSystem = new Filesystem(); + + $temporaryPath = $fileSystem->tempnam(sys_get_temp_dir(), 'symfonyManagerRegistryTest'); + $fileSystem->remove($temporaryPath); + $fileSystem->mkdir($temporaryPath); + + $dumper = new PhpDumper($container); + + $dumper->setProxyDumper(new ProxyDumper()); + $containerFiles = $dumper->dump([ + 'class' => 'LazyServiceProjectAsFilesServiceContainer', + 'as_files' => true, + ]); + + array_walk( + $containerFiles, + static function (string $containerSources, string $fileName) use ($temporaryPath): void { + (new Filesystem())->dumpFile($temporaryPath.'/'.$fileName, $containerSources); + } + ); + + require $temporaryPath.'/LazyServiceProjectAsFilesServiceContainer.php'; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php b/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php index dd7dabcc87db1..e1948356df648 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php @@ -12,27 +12,29 @@ namespace Symfony\Bridge\Doctrine\Tests; use PHPUnit\Framework\TestCase; -use ProxyManager\Proxy\LazyLoadingInterface; -use ProxyManager\Proxy\ValueHolderInterface; -use Symfony\Bridge\Doctrine\ManagerRegistry; -use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; -use Symfony\Bridge\ProxyManager\Tests\LazyProxy\Dumper\PhpDumperTest; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\VarExporter\LazyObjectInterface; class ManagerRegistryTest extends TestCase { public static function setUpBeforeClass(): void { - $test = new PhpDumperTest(); - $test->testDumpContainerWithProxyServiceWillShareProxies(); + $container = new ContainerBuilder(); + + $container->register('foo', \stdClass::class)->setPublic(true); + $container->getDefinition('foo')->setLazy(true)->addTag('proxy', ['interface' => \stdClass::class]); + $container->compile(); + + $dumper = new PhpDumper($container); + eval('?>'.$dumper->dump(['class' => 'LazyServiceDoctrineBridgeContainer'])); } public function testResetService() { - $container = new \LazyServiceProjectServiceContainer(); + $container = new \LazyServiceDoctrineBridgeContainer(); $registry = new TestManagerRegistry('name', [], ['defaultManager' => 'foo'], 'defaultConnection', 'defaultManager', 'proxyInterfaceName'); $registry->setTestContainer($container); @@ -59,10 +61,10 @@ public function testResetService() public function testResetServiceWillNotNestFurtherLazyServicesWithinEachOther() { // This test scenario only applies to containers composed as a set of generated sources - $this->dumpLazyServiceProjectAsFilesServiceContainer(); + $this->dumpLazyServiceDoctrineBridgeContainerAsFiles(); /** @var ContainerInterface $container */ - $container = new \LazyServiceProjectAsFilesServiceContainer(); + $container = new \LazyServiceDoctrineBridgeContainerAsFiles(); $registry = new TestManagerRegistry( 'irrelevant', @@ -77,27 +79,25 @@ public function testResetServiceWillNotNestFurtherLazyServicesWithinEachOther() $service = $container->get('foo'); self::assertInstanceOf(\stdClass::class, $service); - self::assertInstanceOf(LazyLoadingInterface::class, $service); - self::assertInstanceOf(ValueHolderInterface::class, $service); - self::assertFalse($service->isProxyInitialized()); + self::assertInstanceOf(LazyObjectInterface::class, $service); + self::assertFalse($service->isLazyObjectInitialized()); - $service->initializeProxy(); + $service->initializeLazyObject(); self::assertTrue($container->initialized('foo')); - self::assertTrue($service->isProxyInitialized()); + self::assertTrue($service->isLazyObjectInitialized()); $registry->resetManager(); - $service->initializeProxy(); + $service->initializeLazyObject(); - $wrappedValue = $service->getWrappedValueHolderValue(); + $wrappedValue = $service->initializeLazyObject(); self::assertInstanceOf(\stdClass::class, $wrappedValue); - self::assertNotInstanceOf(LazyLoadingInterface::class, $wrappedValue); - self::assertNotInstanceOf(ValueHolderInterface::class, $wrappedValue); + self::assertNotInstanceOf(LazyObjectInterface::class, $wrappedValue); } - private function dumpLazyServiceProjectAsFilesServiceContainer() + private function dumpLazyServiceDoctrineBridgeContainerAsFiles() { - if (class_exists(\LazyServiceProjectAsFilesServiceContainer::class, false)) { + if (class_exists(\LazyServiceDoctrineBridgeContainerAsFiles::class, false)) { return; } @@ -105,7 +105,8 @@ private function dumpLazyServiceProjectAsFilesServiceContainer() $container->register('foo', \stdClass::class) ->setPublic(true) - ->setLazy(true); + ->setLazy(true) + ->addTag('proxy', ['interface' => \stdClass::class]); $container->compile(); $fileSystem = new Filesystem(); @@ -116,9 +117,8 @@ private function dumpLazyServiceProjectAsFilesServiceContainer() $dumper = new PhpDumper($container); - $dumper->setProxyDumper(new ProxyDumper()); $containerFiles = $dumper->dump([ - 'class' => 'LazyServiceProjectAsFilesServiceContainer', + 'class' => 'LazyServiceDoctrineBridgeContainerAsFiles', 'as_files' => true, ]); @@ -129,19 +129,6 @@ static function (string $containerSources, string $fileName) use ($temporaryPath } ); - require $temporaryPath.'/LazyServiceProjectAsFilesServiceContainer.php'; - } -} - -class TestManagerRegistry extends ManagerRegistry -{ - public function setTestContainer($container) - { - $this->container = $container; - } - - public function getAliasNamespace($alias): string - { - return 'Foo'; + require $temporaryPath.'/LazyServiceDoctrineBridgeContainerAsFiles.php'; } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/TestManagerRegistry.php b/src/Symfony/Bridge/Doctrine/Tests/TestManagerRegistry.php new file mode 100644 index 0000000000000..aae7ca27df6b2 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/TestManagerRegistry.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests; + +use Symfony\Bridge\Doctrine\ManagerRegistry; + +class TestManagerRegistry extends ManagerRegistry +{ + public function setTestContainer($container) + { + $this->container = $container; + } + + public function getAliasNamespace($alias): string + { + return 'Foo'; + } +} diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 5cc4a280a47b0..318fc379711e1 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -28,14 +28,13 @@ "symfony/stopwatch": "^5.4|^6.0", "symfony/cache": "^5.4|^6.0", "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", + "symfony/dependency-injection": "^6.2", "symfony/form": "^5.4.9|^6.0.9", "symfony/http-kernel": "^6.2", "symfony/messenger": "^5.4|^6.0", "symfony/doctrine-messenger": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", "symfony/property-info": "^5.4|^6.0", - "symfony/proxy-manager-bridge": "^5.4|^6.0", "symfony/security-core": "^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/uid": "^5.4|^6.0", @@ -55,7 +54,7 @@ "doctrine/orm": "<2.7.4", "phpunit/phpunit": "<5.4.3", "symfony/cache": "<5.4", - "symfony/dependency-injection": "<5.4", + "symfony/dependency-injection": "<6.2", "symfony/form": "<5.4", "symfony/http-kernel": "<6.2", "symfony/messenger": "<5.4", diff --git a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md index 3435a4a186494..5ba6cdaf730a1 100644 --- a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md +++ b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate the bridge + 4.2.0 ----- diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php index 59fcdc022efce..590dc2108e372 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -21,10 +21,14 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; +trigger_deprecation('symfony/proxy-manager-bridge', '6.3', 'The "symfony/proxy-manager-bridge" package is deprecated and can be removed from your dependencies.'); + /** * Runtime lazy loading proxy generator. * * @author Marco Pivetta + * + * @deprecated since Symfony 6.3 */ class RuntimeInstantiator implements InstantiatorInterface { diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index 82ff95dc4cc93..b5f3560a4f401 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -17,11 +17,15 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; +trigger_deprecation('symfony/proxy-manager-bridge', '6.3', 'The "symfony/proxy-manager-bridge" package is deprecated and can be removed from your dependencies.'); + /** * Generates dumped PHP code of proxies via reflection. * * @author Marco Pivetta * + * @deprecated since Symfony 6.3 + * * @final */ class ProxyDumper implements DumperInterface diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php index 0967f1b1d5b23..b3a470ac0490a 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php @@ -24,6 +24,8 @@ * with the ProxyManager bridge. * * @author Marco Pivetta + * + * @group legacy */ class ContainerBuilderTest extends TestCase { diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php index aedfff33c56c5..32992796c0ebf 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php @@ -22,6 +22,8 @@ * with the ProxyManager bridge. * * @author Marco Pivetta + * + * @group legacy */ class PhpDumperTest extends TestCase { diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php index fc04526ced826..fe76f50c53284 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php @@ -22,6 +22,8 @@ * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator}. * * @author Marco Pivetta + * + * @group legacy */ class RuntimeInstantiatorTest extends TestCase { diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index 2d7428fac8d4d..61630b3e46938 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -20,6 +20,8 @@ * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper}. * * @author Marco Pivetta + * + * @group legacy */ class ProxyDumperTest extends TestCase { diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index 1e4333d8ad96d..23218d85df43b 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -18,7 +18,8 @@ "require": { "php": ">=8.1", "friendsofphp/proxy-manager-lts": "^1.0.2", - "symfony/dependency-injection": "^6.2" + "symfony/dependency-injection": "^6.2", + "symfony/deprecation-contracts": "^2.1|^3" }, "require-dev": { "symfony/config": "^6.1" From f3a9a0e2f22bd67074b596493a7e94779eb356e3 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 8 Dec 2022 08:22:17 +0100 Subject: [PATCH 040/475] Fix Composer package name --- src/Symfony/Component/Notifier/Bridge/LineNotify/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/composer.json b/src/Symfony/Component/Notifier/Bridge/LineNotify/composer.json index 43a173bf48812..cc0d90774fb73 100644 --- a/src/Symfony/Component/Notifier/Bridge/LineNotify/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/composer.json @@ -1,5 +1,5 @@ { - "name": "symfony/line-notifier", + "name": "symfony/line-notify-notifier", "type": "symfony-notifier-bridge", "description": "Provides LINE Notify integration for Symfony Notifier.", "keywords": ["line", "notifier", "chat"], From 87a7058f6513b4642dd97c2b24b7ca818b388e57 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Thu, 8 Dec 2022 14:58:23 +0100 Subject: [PATCH 041/475] [Notifier] Remove dependency `symfony/uid` on Notifier bridges --- .../ContactEveryone/Tests/ContactEveryoneTransportTest.php | 3 +-- .../Component/Notifier/Bridge/ContactEveryone/composer.json | 3 --- .../Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php | 3 +-- src/Symfony/Component/Notifier/Bridge/Esendex/composer.json | 3 --- src/Symfony/Component/Notifier/Bridge/OrangeSms/composer.json | 3 --- .../Component/Notifier/Bridge/SmsFactor/SmsFactorTransport.php | 3 +-- src/Symfony/Component/Notifier/Bridge/SmsFactor/composer.json | 3 +-- 7 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/ContactEveryone/Tests/ContactEveryoneTransportTest.php b/src/Symfony/Component/Notifier/Bridge/ContactEveryone/Tests/ContactEveryoneTransportTest.php index fef60a4c1c0d9..97194efdd87be 100644 --- a/src/Symfony/Component/Notifier/Bridge/ContactEveryone/Tests/ContactEveryoneTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/ContactEveryone/Tests/ContactEveryoneTransportTest.php @@ -18,7 +18,6 @@ use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Component\Notifier\Test\TransportTestCase; -use Symfony\Component\Uid\Uuid; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; @@ -47,7 +46,7 @@ public function unsupportedMessagesProvider(): iterable public function testSendSuccessfully() { - $messageId = Uuid::v4()->toRfc4122(); + $messageId = bin2hex(random_bytes(7)); $response = $this->createMock(ResponseInterface::class); $response->method('getStatusCode')->willReturn(200); $response->method('getContent')->willReturn($messageId); diff --git a/src/Symfony/Component/Notifier/Bridge/ContactEveryone/composer.json b/src/Symfony/Component/Notifier/Bridge/ContactEveryone/composer.json index 8f765ab71f1f8..f47e713dcc63f 100644 --- a/src/Symfony/Component/Notifier/Bridge/ContactEveryone/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/ContactEveryone/composer.json @@ -20,9 +20,6 @@ "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, - "require-dev": { - "symfony/uid": "^5.4|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\ContactEveryone\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php index 4d197428815d7..cde836ae55607 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/Tests/EsendexTransportTest.php @@ -18,7 +18,6 @@ use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Component\Notifier\Test\TransportTestCase; -use Symfony\Component\Uid\Uuid; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; @@ -88,7 +87,7 @@ public function testSendWithErrorResponseContainingDetailsThrowsTransportExcepti public function testSendWithSuccessfulResponseDispatchesMessageEvent() { - $messageId = Uuid::v4()->toRfc4122(); + $messageId = bin2hex(random_bytes(7)); $response = $this->createMock(ResponseInterface::class); $response->expects($this->exactly(2)) ->method('getStatusCode') diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json b/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json index 63422b2d5d40c..e6ea519dcfa87 100644 --- a/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Esendex/composer.json @@ -20,9 +20,6 @@ "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, - "require-dev": { - "symfony/uid": "^5.4|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Esendex\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/OrangeSms/composer.json b/src/Symfony/Component/Notifier/Bridge/OrangeSms/composer.json index 72f9dffc56bac..ee2fd7443d254 100644 --- a/src/Symfony/Component/Notifier/Bridge/OrangeSms/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/OrangeSms/composer.json @@ -20,9 +20,6 @@ "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, - "require-dev": { - "symfony/uid": "^5.4|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\OrangeSms\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/SmsFactor/SmsFactorTransport.php b/src/Symfony/Component/Notifier/Bridge/SmsFactor/SmsFactorTransport.php index 494ac96ad8f28..0032d3c9510f8 100644 --- a/src/Symfony/Component/Notifier/Bridge/SmsFactor/SmsFactorTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/SmsFactor/SmsFactorTransport.php @@ -17,7 +17,6 @@ use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Component\Notifier\Transport\AbstractTransport; -use Symfony\Component\Uid\Uuid; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -67,7 +66,7 @@ protected function doSend(MessageInterface $message): SentMessage throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); } - $messageId = Uuid::v4()->toRfc4122(); + $messageId = bin2hex(random_bytes(7));; $query = [ 'to' => $message->getPhone(), 'text' => $message->getSubject(), diff --git a/src/Symfony/Component/Notifier/Bridge/SmsFactor/composer.json b/src/Symfony/Component/Notifier/Bridge/SmsFactor/composer.json index 6a7b8d6d09490..04ae90a40d137 100644 --- a/src/Symfony/Component/Notifier/Bridge/SmsFactor/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/SmsFactor/composer.json @@ -18,8 +18,7 @@ "require": { "php": ">=8.1", "symfony/http-client": "^5.4|^6.0", - "symfony/notifier": "^6.2", - "symfony/uid": "^5.4|^6.0" + "symfony/notifier": "^6.2" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\SmsFactor\\": "" }, From 88e6c3e922606a8f5023de7b0d919d0f89993fdd Mon Sep 17 00:00:00 2001 From: Benjamin Schoch Date: Thu, 8 Dec 2022 12:55:49 +0100 Subject: [PATCH 042/475] [Notifier] [FakeSms] Allow missing optional dependency --- .../FakeSms/FakeSmsTransportFactory.php | 20 ++++++- .../Tests/FakeSmsTransportFactoryTest.php | 56 +++++++++++++++++++ .../Notifier/Bridge/FakeSms/composer.json | 7 ++- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsTransportFactory.php index 9a2fa84d93075..b97ff34ef8f82 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeSms/FakeSmsTransportFactory.php @@ -13,6 +13,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; use Symfony\Component\Notifier\Transport\AbstractTransportFactory; use Symfony\Component\Notifier\Transport\Dsn; @@ -24,10 +25,10 @@ */ final class FakeSmsTransportFactory extends AbstractTransportFactory { - private MailerInterface $mailer; - private LoggerInterface $logger; + private ?MailerInterface $mailer; + private ?LoggerInterface $logger; - public function __construct(MailerInterface $mailer, LoggerInterface $logger) + public function __construct(MailerInterface $mailer = null, LoggerInterface $logger = null) { parent::__construct(); @@ -40,6 +41,10 @@ public function create(Dsn $dsn): FakeSmsEmailTransport|FakeSmsLoggerTransport $scheme = $dsn->getScheme(); if ('fakesms+email' === $scheme) { + if (null === $this->mailer) { + $this->throwMissingDependencyException($scheme, MailerInterface::class, 'symfony/mailer'); + } + $mailerTransport = $dsn->getHost(); $to = $dsn->getRequiredOption('to'); $from = $dsn->getRequiredOption('from'); @@ -48,6 +53,10 @@ public function create(Dsn $dsn): FakeSmsEmailTransport|FakeSmsLoggerTransport } if ('fakesms+logger' === $scheme) { + if (null === $this->logger) { + $this->throwMissingDependencyException($scheme, LoggerInterface::class, 'psr/log'); + } + return new FakeSmsLoggerTransport($this->logger); } @@ -58,4 +67,9 @@ protected function getSupportedSchemes(): array { return ['fakesms+email', 'fakesms+logger']; } + + private function throwMissingDependencyException(string $scheme, string $missingDependency, string $suggestedPackage): void + { + throw new LogicException(sprintf('Cannot create a transport for scheme "%s" without providing an implementation of "%s". Try running "composer require "%s"".', $scheme, $missingDependency, $suggestedPackage)); + } } diff --git a/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsTransportFactoryTest.php index 0d18d51c5303d..7c017375cc098 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsTransportFactoryTest.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeSms/Tests/FakeSmsTransportFactoryTest.php @@ -14,10 +14,35 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; +use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Test\TransportFactoryTestCase; +use Symfony\Component\Notifier\Transport\Dsn; final class FakeSmsTransportFactoryTest extends TransportFactoryTestCase { + /** + * @dataProvider missingRequiredDependencyProvider + */ + public function testMissingRequiredDependency(?MailerInterface $mailer, ?LoggerInterface $logger, string $dsn, string $message) + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage($message); + + $factory = new FakeSmsTransportFactory($mailer, $logger); + $factory->create(new Dsn($dsn)); + } + + /** + * @dataProvider missingOptionalDependencyProvider + */ + public function testMissingOptionalDependency(?MailerInterface $mailer, ?LoggerInterface $logger, string $dsn) + { + $factory = new FakeSmsTransportFactory($mailer, $logger); + $transport = $factory->create(new Dsn($dsn)); + + $this->assertSame($dsn, (string) $transport); + } + public function createFactory(): FakeSmsTransportFactory { return new FakeSmsTransportFactory($this->createMock(MailerInterface::class), $this->createMock(LoggerInterface::class)); @@ -63,4 +88,35 @@ public function unsupportedSchemeProvider(): iterable { yield ['somethingElse://default?to=recipient@email.net&from=sender@email.net']; } + + public function missingRequiredDependencyProvider(): iterable + { + $exceptionMessage = 'Cannot create a transport for scheme "%s" without providing an implementation of "%s".'; + yield 'missing mailer' => [ + null, + $this->createMock(LoggerInterface::class), + 'fakesms+email://default?to=recipient@email.net&from=sender@email.net', + sprintf($exceptionMessage, 'fakesms+email', MailerInterface::class), + ]; + yield 'missing logger' => [ + $this->createMock(MailerInterface::class), + null, + 'fakesms+logger://default', + sprintf($exceptionMessage, 'fakesms+logger', LoggerInterface::class), + ]; + } + + public function missingOptionalDependencyProvider(): iterable + { + yield 'missing logger' => [ + $this->createMock(MailerInterface::class), + null, + 'fakesms+email://default?to=recipient@email.net&from=sender@email.net', + ]; + yield 'missing mailer' => [ + null, + $this->createMock(LoggerInterface::class), + 'fakesms+logger://default', + ]; + } } diff --git a/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json b/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json index 5ec84dd6dd401..1fd9205d2ccde 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json @@ -24,8 +24,11 @@ "php": ">=8.1", "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/mailer": "^5.4|^6.0" + "symfony/event-dispatcher-contracts": "^2|^3" + }, + "require-dev": { + "symfony/mailer": "^5.4|^6.0", + "psr/log": "^1|^2|^3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\FakeSms\\": "" }, From 1d56613ecbe42eaeb900f79b2c5fc136159b4cc1 Mon Sep 17 00:00:00 2001 From: curlycarla2004 Date: Mon, 21 Nov 2022 11:40:30 +0100 Subject: [PATCH 043/475] [DomCrawler][FrameworkBundle] Add `assertSelectorCount` --- .../Bundle/FrameworkBundle/CHANGELOG.md | 5 +++ .../Test/DomCrawlerAssertionsTrait.php | 5 +++ .../Tests/Test/WebTestCaseTest.php | 10 +++++ .../Bundle/FrameworkBundle/composer.json | 2 +- src/Symfony/Component/DomCrawler/CHANGELOG.md | 5 +++ .../Test/Constraint/CrawlerSelectorCount.php | 45 +++++++++++++++++++ 6 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorCount.php diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 1d43a095ed1c5..f6cfae42e6ac1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add `DomCrawlerAssertionsTrait::assertSelectorCount(int $count, string $selector)` + 6.2 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php index 2a692d6f5a367..e61d1cd77c09f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php @@ -35,6 +35,11 @@ public static function assertSelectorNotExists(string $selector, string $message self::assertThat(self::getCrawler(), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorExists($selector)), $message); } + public static function assertSelectorCount(int $expectedCount, string $selector, string $message = ''): void + { + self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorCount($expectedCount, $selector), $message); + } + public static function assertSelectorTextContains(string $selector, string $text, string $message = ''): void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php index 8c43917df6f5e..ba3b404364342 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php @@ -190,6 +190,16 @@ public function testAssertSelectorNotExists() $this->getCrawlerTester(new Crawler('

'))->assertSelectorNotExists('body > h1'); } + public function testAssertSelectorCount() + { + $this->getCrawlerTester(new Crawler('

Hello

'))->assertSelectorCount(1, 'p'); + $this->getCrawlerTester(new Crawler('

Hello

Foo

'))->assertSelectorCount(2, 'p'); + $this->getCrawlerTester(new Crawler('

This is not a paragraph.

'))->assertSelectorCount(0, 'p'); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Failed asserting that the Crawler selector "p" was expected to be found 0 time(s) but was found 1 time(s).'); + $this->getCrawlerTester(new Crawler('

Hello

'))->assertSelectorCount(0, 'p'); + } + public function testAssertSelectorTextNotContains() { $this->getCrawlerTester(new Crawler('

Foo'))->assertSelectorTextNotContains('body > h1', 'Bar'); diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 4ab6b2e919e32..2c62c6e3baee5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -39,7 +39,7 @@ "symfony/browser-kit": "^5.4|^6.0", "symfony/console": "^5.4.9|^6.0.9", "symfony/css-selector": "^5.4|^6.0", - "symfony/dom-crawler": "^5.4|^6.0", + "symfony/dom-crawler": "^6.3", "symfony/dotenv": "^5.4|^6.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/form": "^5.4|^6.0", diff --git a/src/Symfony/Component/DomCrawler/CHANGELOG.md b/src/Symfony/Component/DomCrawler/CHANGELOG.md index 1254296490e62..a05fb7f2ebec8 100644 --- a/src/Symfony/Component/DomCrawler/CHANGELOG.md +++ b/src/Symfony/Component/DomCrawler/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add `CrawlerSelectorCount` test constraint + 6.0 --- diff --git a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorCount.php b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorCount.php new file mode 100644 index 0000000000000..22ee1db078a2e --- /dev/null +++ b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorCount.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\DomCrawler\Crawler; + +final class CrawlerSelectorCount extends Constraint +{ + public function __construct( + private readonly int $count, + private readonly string $selector, + ) { + } + + public function toString(): string + { + return sprintf('selector "%s" count is "%d"', $this->selector, $this->count); + } + + /** + * @param Crawler $crawler + */ + protected function matches($crawler): bool + { + return $this->count === \count($crawler->filter($this->selector)); + } + + /** + * @param Crawler $crawler + */ + protected function failureDescription($crawler): string + { + return sprintf('the Crawler selector "%s" was expected to be found %d time(s) but was found %d time(s)', $this->selector, $this->count, \count($crawler->filter($this->selector))); + } +} From bb7779d2627e170cdb8433e7d4491d86967d0b74 Mon Sep 17 00:00:00 2001 From: Kuzia Date: Wed, 23 Nov 2022 16:16:04 +0300 Subject: [PATCH 044/475] [Console] #47809 remove exit() call in last SignalHandler --- src/Symfony/Component/Console/Application.php | 9 +---- src/Symfony/Component/Console/CHANGELOG.md | 5 +++ .../Console/Tests/ApplicationTest.php | 40 +++++++++++++++++-- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 99548faf858b8..0fed82c8f082a 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -998,15 +998,8 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI foreach ($this->signalsToDispatchEvent as $signal) { $event = new ConsoleSignalEvent($command, $input, $output, $signal); - $this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) { + $this->signalRegistry->register($signal, function () use ($event) { $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); - - // No more handlers, we try to simulate PHP default behavior - if (!$hasNext) { - if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) { - exit(0); - } - } }); } } diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index 61c36b0e06ab7..66b30ccb9f6db 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Remove `exit` call in `Application` signal handlers. Commands will no longer be automatically interrupted after receiving signal other than `SIGUSR1` or `SIGUSR2` + 6.2 --- diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index c178151688928..da72496d1ee32 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -2014,6 +2014,19 @@ public function testSignalableCommandHandlerCalledAfterEventListener() $this->assertSame([SignalEventSubscriber::class, SignableCommand::class], $command->signalHandlers); } + public function testSignalableCommandDoesNotInterruptedOnTermSignals() + { + $command = new TerminatableCommand(true, \SIGINT); + $command->exitCode = 129; + + $dispatcher = new EventDispatcher(); + $application = new Application(); + $application->setAutoExit(false); + $application->setDispatcher($dispatcher); + $application->add($command); + $this->assertSame(129, $application->run(new ArrayInput(['signal']))); + } + /** * @group tty */ @@ -2113,26 +2126,31 @@ public function isEnabled(): bool class BaseSignableCommand extends Command { public $signaled = false; + public $exitCode = 1; public $signalHandlers = []; public $loop = 1000; private $emitsSignal; + private $signal; - public function __construct(bool $emitsSignal = true) + protected static $defaultName = 'signal'; + + public function __construct(bool $emitsSignal = true, int $signal = \SIGUSR1) { parent::__construct(); $this->emitsSignal = $emitsSignal; + $this->signal = $signal; } protected function execute(InputInterface $input, OutputInterface $output): int { if ($this->emitsSignal) { - posix_kill(posix_getpid(), \SIGUSR1); + posix_kill(posix_getpid(), $this->signal); } for ($i = 0; $i < $this->loop; ++$i) { usleep(100); if ($this->signaled) { - return 1; + return $this->exitCode; } } @@ -2155,6 +2173,22 @@ public function handleSignal(int $signal): void } } +class TerminatableCommand extends BaseSignableCommand implements SignalableCommandInterface +{ + protected static $defaultName = 'signal'; + + public function getSubscribedSignals(): array + { + return SignalRegistry::isSupported() ? [\SIGINT] : []; + } + + public function handleSignal(int $signal): void + { + $this->signaled = true; + $this->signalHandlers[] = __CLASS__; + } +} + class SignalEventSubscriber implements EventSubscriberInterface { public $signaled = false; From 369b051e28f12e29212da8ea77ddb8bd6ed0f118 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Thu, 17 Nov 2022 23:08:19 +0100 Subject: [PATCH 045/475] [Validator] Add pattern in Regex constraint violations --- src/Symfony/Component/Validator/CHANGELOG.md | 1 + src/Symfony/Component/Validator/Constraints/RegexValidator.php | 1 + .../Validator/Tests/Constraints/RegexValidatorTest.php | 2 ++ 3 files changed, 4 insertions(+) diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 88b152ed8dc31..ca83f3af0a3f1 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `Uuid::TIME_BASED_VERSIONS` to match that a UUID being validated embeds a timestamp + * Add the `pattern` parameter in violations of the `Regex` constraint 6.2 --- diff --git a/src/Symfony/Component/Validator/Constraints/RegexValidator.php b/src/Symfony/Component/Validator/Constraints/RegexValidator.php index 177fcb69cd107..b31431bedd20d 100644 --- a/src/Symfony/Component/Validator/Constraints/RegexValidator.php +++ b/src/Symfony/Component/Validator/Constraints/RegexValidator.php @@ -47,6 +47,7 @@ public function validate(mixed $value, Constraint $constraint) if ($constraint->match xor preg_match($constraint->pattern, $value)) { $this->context->buildViolation($constraint->message) ->setParameter('{{ value }}', $this->formatValue($value)) + ->setParameter('{{ pattern }}', $constraint->pattern) ->setCode(Regex::REGEX_FAILED_ERROR) ->addViolation(); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/RegexValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/RegexValidatorTest.php index 166de5aa95827..267edf0c1bcdd 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/RegexValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/RegexValidatorTest.php @@ -118,6 +118,7 @@ public function testInvalidValues($value) $this->buildViolation('myMessage') ->setParameter('{{ value }}', '"'.$value.'"') + ->setParameter('{{ pattern }}', '/^[0-9]+$/') ->setCode(Regex::REGEX_FAILED_ERROR) ->assertRaised(); } @@ -133,6 +134,7 @@ public function testInvalidValuesNamed($value) $this->buildViolation('myMessage') ->setParameter('{{ value }}', '"'.$value.'"') + ->setParameter('{{ pattern }}', '/^[0-9]+$/') ->setCode(Regex::REGEX_FAILED_ERROR) ->assertRaised(); } From c5fdfbcc4a7dcd146ddb1d7257bae665daaf0d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 11 Nov 2022 23:37:29 +0100 Subject: [PATCH 046/475] Add placeholder formatters per ProgressBar instance --- src/Symfony/Component/Console/CHANGELOG.md | 1 + .../Component/Console/Helper/ProgressBar.php | 29 ++++++++++++++++--- .../Console/Tests/Helper/ProgressBarTest.php | 23 +++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index 66b30ccb9f6db..0e010fae62ef0 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Remove `exit` call in `Application` signal handlers. Commands will no longer be automatically interrupted after receiving signal other than `SIGUSR1` or `SIGUSR2` + * Add `ProgressBar::setPlaceholderFormatter` to set a placeholder attached to a instance, instead of being global. 6.2 --- diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index b65bba2267315..8dad297d5e855 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -60,6 +60,7 @@ final class ProgressBar private Terminal $terminal; private ?string $previousMessage = null; private Cursor $cursor; + private array $placeholders = []; private static array $formatters; private static array $formats; @@ -95,12 +96,12 @@ public function __construct(OutputInterface $output, int $max = 0, float $minSec } /** - * Sets a placeholder formatter for a given name. + * Sets a placeholder formatter for a given name, globally for all instances of ProgressBar. * * This method also allow you to override an existing placeholder. * - * @param string $name The placeholder name (including the delimiter char like %) - * @param callable $callable A PHP callable + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable(ProgressBar):string $callable A PHP callable */ public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void { @@ -121,6 +122,26 @@ public static function getPlaceholderFormatterDefinition(string $name): ?callabl return self::$formatters[$name] ?? null; } + /** + * Sets a placeholder formatter for a given name, for this instance only. + * + * @param callable(ProgressBar):string $callable A PHP callable + */ + public function setPlaceholderFormatter(string $name, callable $callable): void + { + $this->placeholders[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + */ + public function getPlaceholderFormatter(string $name): ?callable + { + return $this->placeholders[$name] ?? $this::getPlaceholderFormatterDefinition($name); + } + /** * Sets a format for a given name. * @@ -573,7 +594,7 @@ private function buildLine(): string $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; $callback = function ($matches) { - if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { + if ($formatter = $this->getPlaceholderFormatter($matches[1])) { $text = $formatter($this, $this->output); } elseif (isset($this->messages[$matches[1]])) { $text = $this->messages[$matches[1]]; diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index b8d29a1e8969e..3a44ba6626259 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -861,6 +861,29 @@ public function testAddingPlaceholderFormatter() ); } + public function testAddingInstancePlaceholderFormatter() + { + $bar = new ProgressBar($output = $this->getOutputStream(), 3, 0); + $bar->setFormat(' %countdown% [%bar%]'); + $bar->setPlaceholderFormatter('countdown', $function = function (ProgressBar $bar) { + return $bar->getMaxSteps() - $bar->getProgress(); + }); + + $this->assertSame($function, $bar->getPlaceholderFormatter('countdown')); + + $bar->start(); + $bar->advance(); + $bar->finish(); + + rewind($output->getStream()); + $this->assertEquals( + ' 3 [>---------------------------]'. + $this->generateOutput(' 2 [=========>------------------]'). + $this->generateOutput(' 0 [============================]'), + stream_get_contents($output->getStream()) + ); + } + public function testMultilineFormat() { $bar = new ProgressBar($output = $this->getOutputStream(), 3, 0); From a3b7fca9828f0ca5f4aeccaa8b48b06c07968bd6 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 3 Dec 2022 23:30:11 +0100 Subject: [PATCH 047/475] [DependencyInjection] Use WeakReference to break circular references in the container --- .../LazyProxy/PhpDumper/ProxyDumper.php | 7 +- .../Fixtures/php/lazy_service_structure.txt | 11 +- .../PhpDumper/Fixtures/proxy-factory.php | 10 +- .../LazyProxy/PhpDumper/ProxyDumperTest.php | 13 +- src/Symfony/Bridge/ProxyManager/composer.json | 2 +- .../DependencyInjection/Container.php | 38 +- .../DependencyInjection/Dumper/PhpDumper.php | 125 ++++--- .../ExpressionLanguageProvider.php | 6 +- .../LazyProxy/PhpDumper/LazyServiceDumper.php | 12 +- .../Tests/Dumper/PhpDumperTest.php | 1 + .../Tests/Fixtures/php/closure.php | 6 +- ...er_class_constructor_without_arguments.php | 2 + ...s_with_mandatory_constructor_arguments.php | 2 + ...ss_with_optional_constructor_arguments.php | 2 + ...om_container_class_without_constructor.php | 2 + .../Tests/Fixtures/php/services1-1.php | 2 + .../Tests/Fixtures/php/services1.php | 2 + .../Tests/Fixtures/php/services10.php | 6 +- .../Fixtures/php/services10_as_files.txt | 12 +- .../Tests/Fixtures/php/services12.php | 6 +- .../Tests/Fixtures/php/services13.php | 8 +- .../Tests/Fixtures/php/services19.php | 13 +- .../Tests/Fixtures/php/services24.php | 6 +- .../Tests/Fixtures/php/services26.php | 19 +- .../Tests/Fixtures/php/services33.php | 10 +- .../Tests/Fixtures/php/services8.php | 2 + .../Tests/Fixtures/php/services9_as_files.txt | 42 ++- .../Tests/Fixtures/php/services9_compiled.php | 156 ++++---- .../php/services9_inlined_factories.txt | 180 +++++---- .../php/services9_lazy_inlined_factories.txt | 14 +- .../Tests/Fixtures/php/services_adawson.php | 26 +- .../php/services_almost_circular_private.php | 236 ++++++------ .../php/services_almost_circular_public.php | 354 +++++++++--------- .../Fixtures/php/services_array_params.php | 8 +- .../Fixtures/php/services_base64_env.php | 5 +- .../services_closure_argument_compiled.php | 24 +- .../Tests/Fixtures/php/services_csv_env.php | 5 +- .../Fixtures/php/services_dedup_lazy.php E377 | 26 +- .../Fixtures/php/services_deep_graph.php | 18 +- .../Fixtures/php/services_default_env.php | 9 +- .../Tests/Fixtures/php/services_env_in_id.php | 10 +- .../php/services_errored_definition.php | 156 ++++---- .../Fixtures/php/services_inline_requires.php | 16 +- .../Fixtures/php/services_inline_self_ref.php | 6 +- .../Tests/Fixtures/php/services_json_env.php | 7 +- .../Tests/Fixtures/php/services_locator.php | 72 ++-- .../php/services_new_in_initializer.php | 6 +- .../php/services_non_shared_duplicates.php | 18 +- .../Fixtures/php/services_non_shared_lazy.php | 12 +- .../php/services_non_shared_lazy_as_files.txt | 8 +- .../php/services_non_shared_lazy_ghost.php | 14 +- .../Fixtures/php/services_private_frozen.php | 10 +- .../php/services_private_in_expression.php | 6 +- .../php/services_query_string_env.php | 5 +- .../Tests/Fixtures/php/services_rot13_env.php | 15 +- .../php/services_service_locator_argument.php | 26 +- .../Fixtures/php/services_subscriber.php | 22 +- .../Tests/Fixtures/php/services_tsantos.php | 6 +- .../php/services_uninitialized_ref.php | 56 +-- .../php/services_unsupported_characters.php | 14 +- .../Tests/Fixtures/php/services_url_env.php | 5 +- .../Tests/Fixtures/php/services_wither.php | 6 +- .../Fixtures/php/services_wither_lazy.php | 8 +- .../php/services_wither_staticreturntype.php | 6 +- .../DependencyInjection/composer.json | 2 +- 65 files changed, 1097 insertions(+), 843 deletions(-) diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index b5f3560a4f401..fd610860f534f 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -53,7 +53,7 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ $instantiation = 'return'; if ($definition->isShared()) { - $instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); + $instantiation .= sprintf(' $container->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); } $proxifiedClass = new \ReflectionClass($this->proxyGenerator->getProxifiedClass($definition)); @@ -61,8 +61,9 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ return <<createProxy('$proxyClass', function () { - return \\$proxyClass::staticProxyConstructor(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) { + $instantiation \$container->createProxy('$proxyClass', static function () use (\$containerRef) { + return \\$proxyClass::staticProxyConstructor(static function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$containerRef) { + \$container = \$containerRef->get(); \$wrappedInstance = $factoryCode; \$proxy->setProxyInitializer(null); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt index 825c1051ca38f..84995384157b6 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt @@ -3,12 +3,15 @@ use %a class LazyServiceProjectServiceContainer extends Container {%a - protected function getFooService($lazyLoad = true) + protected static function getFooService($container, $lazyLoad = true) { + $containerRef = $container->ref; + if (true === $lazyLoad) { - return $this->services['foo'] = $this->createProxy('stdClass_%s', function () { - return %S\stdClass_%s(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) { - $wrappedInstance = $this->getFooService(false); + return $container->services['foo'] = $container->createProxy('stdClass_%s', static function () use ($containerRef) { + return %S\stdClass_%s(static function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($containerRef) { + $container = $containerRef->get(); + $wrappedInstance = self::getFooService($containerRef->get(), false); $proxy->setProxyInitializer(null); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php index c06cb534b6e79..12a7de3483761 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php @@ -7,10 +7,14 @@ public function getFooService($lazyLoad = true) { + $container = $this; + $containerRef = \WeakReference::create($this); + if (true === $lazyLoad) { - return $this->privates['foo'] = $this->createProxy('SunnyInterface_1eff735', function () { - return \SunnyInterface_1eff735::staticProxyConstructor(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) { - $wrappedInstance = $this->getFooService(false); + return $container->privates['foo'] = $container->createProxy('SunnyInterface_1eff735', static function () use ($containerRef) { + return \SunnyInterface_1eff735::staticProxyConstructor(static function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($containerRef) { + $container = $containerRef->get(); + $wrappedInstance = $container->getFooService(false); $proxy->setProxyInitializer(null); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index 61630b3e46938..7fd212c3f9d3e 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -72,10 +72,10 @@ public function testGetProxyFactoryCode() $definition->setLazy(true); - $code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFoo2Service(false)'); + $code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$container->getFoo2Service(false)'); $this->assertStringMatchesFormat( - '%A$wrappedInstance = $this->getFoo2Service(false);%w$proxy->setProxyInitializer(null);%A', + '%A$wrappedInstance = $container->getFoo2Service(false);%w$proxy->setProxyInitializer(null);%A', $code ); } @@ -87,9 +87,9 @@ public function testCorrectAssigning(Definition $definition, $access) { $definition->setLazy(true); - $code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFoo2Service(false)'); + $code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$container->getFoo2Service(false)'); - $this->assertStringMatchesFormat('%A$this->'.$access.'[\'foo\'] = %A', $code); + $this->assertStringMatchesFormat('%A$container->'.$access.'[\'foo\'] = %A', $code); } public function getPrivatePublicDefinitions() @@ -118,7 +118,7 @@ public function testGetProxyFactoryCodeForInterface() $definition->addTag('proxy', ['interface' => SunnyInterface::class]); $implem = "dumper->getProxyCode($definition); - $factory = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFooService(false)'); + $factory = $this->dumper->getProxyFactoryCode($definition, 'foo', '$container->getFooService(false)'); $factory = <<=8.1", "friendsofphp/proxy-manager-lts": "^1.0.2", - "symfony/dependency-injection": "^6.2", + "symfony/dependency-injection": "^6.3", "symfony/deprecation-contracts": "^2.1|^3" }, "require-dev": { diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 9c830e77c8fe2..1cb5ca7403a76 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -63,6 +63,8 @@ class Container implements ContainerInterface, ResetInterface private bool $compiled = false; private \Closure $getEnv; + private static $make; + public function __construct(ParameterBagInterface $parameterBag = null) { $this->parameterBag = $parameterBag ?? new EnvPlaceholderParameterBag(); @@ -135,7 +137,7 @@ public function set(string $id, ?object $service) if (isset($this->privates['service_container']) && $this->privates['service_container'] instanceof \Closure) { $initialize = $this->privates['service_container']; unset($this->privates['service_container']); - $initialize(); + $initialize($this); } if ('service_container' === $id) { @@ -195,7 +197,7 @@ public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALI { return $this->services[$id] ?? $this->services[$id = $this->aliases[$id] ?? $id] - ?? ('service_container' === $id ? $this : ($this->factories[$id] ?? $this->make(...))($id, $invalidBehavior)); + ?? ('service_container' === $id ? $this : ($this->factories[$id] ?? self::$make ??= self::make(...))($this, $id, $invalidBehavior)); } /** @@ -203,41 +205,41 @@ public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALI * * As a separate method to allow "get()" to use the really fast `??` operator. */ - private function make(string $id, int $invalidBehavior) + private static function make($container, string $id, int $invalidBehavior) { - if (isset($this->loading[$id])) { - throw new ServiceCircularReferenceException($id, array_merge(array_keys($this->loading), [$id])); + if (isset($container->loading[$id])) { + throw new ServiceCircularReferenceException($id, array_merge(array_keys($container->loading), [$id])); } - $this->loading[$id] = true; + $container->loading[$id] = true; try { - if (isset($this->fileMap[$id])) { - return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->load($this->fileMap[$id]); - } elseif (isset($this->methodMap[$id])) { - return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$this->methodMap[$id]}(); + if (isset($container->fileMap[$id])) { + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $container->load($container->fileMap[$id]); + } elseif (isset($container->methodMap[$id])) { + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $container->{$container->methodMap[$id]}($container); } } catch (\Exception $e) { - unset($this->services[$id]); + unset($container->services[$id]); throw $e; } finally { - unset($this->loading[$id]); + unset($container->loading[$id]); } if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) { if (!$id) { throw new ServiceNotFoundException($id); } - if (isset($this->syntheticIds[$id])) { + if (isset($container->syntheticIds[$id])) { throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is synthetic, it needs to be set at boot time before it can be used.', $id)); } - if (isset($this->getRemovedIds()[$id])) { + if (isset($container->getRemovedIds()[$id])) { throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.', $id)); } $alternatives = []; - foreach ($this->getServiceIds() as $knownId) { + foreach ($container->getServiceIds() as $knownId) { if ('' === $knownId || '.' === $knownId[0]) { continue; } @@ -378,13 +380,13 @@ final protected function getService(string|false $registry, string $id, ?string return false !== $registry ? $this->{$registry}[$id] ?? null : null; } if (false !== $registry) { - return $this->{$registry}[$id] ??= $load ? $this->load($method) : $this->{$method}(); + return $this->{$registry}[$id] ??= $load ? $this->load($method) : $this->{$method}($this); } if (!$load) { - return $this->{$method}(); + return $this->{$method}($this); } - return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory() : $this->load($method); + return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory($this) : $this->load($method); } private function __clone() diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index ad7b82c95e3db..0cd43d1504cc2 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -67,7 +67,7 @@ class PhpDumper extends Dumper private int $variableCount; private ?\SplObjectStorage $inlinedDefinitions = null; private ?array $serviceCalls = null; - private array $reservedVariables = ['instance', 'class', 'this', 'container']; + private array $reservedVariables = ['instance', 'class', 'this', 'container', 'containerRef']; private ExpressionLanguage $expressionLanguage; private ?string $targetDirRegex = null; private int $targetDirMaxMatches; @@ -85,6 +85,7 @@ class PhpDumper extends Dumper private array $singleUsePrivateIds = []; private array $preload = []; private bool $addGetService = false; + private bool $addContainerRef = false; private array $locatedIds = []; private string $serviceLocatorTag; private array $exportedVariables = []; @@ -237,8 +238,8 @@ public function dump(array $options = []): string|array if ($this->addGetService) { $code = preg_replace( - "/(\r?\n\r?\n public function __construct.+?\\{\r?\n)/s", - "\n protected \Closure \$getService;$1 \$this->getService = \$this->getService(...);\n", + "/(\r?\n\r?\n public function __construct.+?\\{\r?\n) ++([^\r\n]++)/s", + "\n protected \Closure \$getService;$1 \$containerRef = $2\n \$this->getService = static function () use (\$containerRef) { return \$containerRef->get()->getService(...\\func_get_args()); };", $code, 1 ); @@ -679,7 +680,7 @@ private function addServiceInstance(string $id, Definition $definition, bool $is } if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) { - $instantiation = sprintf('$this->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance'); + $instantiation = sprintf('$container->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance'); } elseif (!$isSimpleInstance) { $instantiation = '$instance'; } @@ -755,7 +756,7 @@ private function addServiceMethodCalls(Definition $definition, string $variableN if ($call[2] ?? false) { if (null !== $sharedNonLazyId && $lastWitherIndex === $k) { - $witherAssignation = sprintf('$this->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId); + $witherAssignation = sprintf('$container->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId); } $witherAssignation .= sprintf('$%s = ', $variableName); } @@ -847,10 +848,11 @@ private function addService(string $id, Definition $definition): array $methodName = $this->generateMethodName($id); if ($asFile || $definition->isLazy()) { - $lazyInitialization = '$lazyLoad = true'; + $lazyInitialization = ', $lazyLoad = true'; } else { $lazyInitialization = ''; } + $this->addContainerRef = false; $code = <<isShared()) { - $factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); + $factory = sprintf('$container->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); } $asGhostObject = false; @@ -903,16 +905,16 @@ protected function {$methodName}($lazyInitialization) $code .= sprintf(' %s ??= ', $factory); if ($asFile) { - $code .= "fn () => self::do(\$container);\n\n"; + $code .= "self::do(...);\n\n"; } else { - $code .= sprintf("\$this->%s(...);\n\n", $methodName); + $code .= sprintf("self::%s(...);\n\n", $methodName); } } $lazyLoad = $asGhostObject ? '$proxy' : 'false'; + $this->addContainerRef = true; - $factoryCode = $asFile ? sprintf('self::do($container, %s)', $lazyLoad) : sprintf('$this->%s(%s)', $methodName, $lazyLoad); - $factoryCode = $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode); - $code .= $asFile ? preg_replace('/function \(([^)]*+)\)( {|:)/', 'function (\1) use ($container)\2', $factoryCode) : $factoryCode; + $factoryCode = $asFile ? sprintf('self::do($containerRef->get(), %s)', $lazyLoad) : sprintf('self::%s($containerRef->get(), %s)', $methodName, $lazyLoad); + $code .= $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode); } $c = $this->addServiceInclude($id, $definition, null !== $isProxyCandidate); @@ -932,24 +934,21 @@ protected function {$methodName}($lazyInitialization) if (!$isProxyCandidate && !$definition->isShared()) { $c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c))); - $lazyloadInitialization = $definition->isLazy() ? '$lazyLoad = true' : ''; + $lazyloadInitialization = $definition->isLazy() ? ', $lazyLoad = true' : ''; - $c = sprintf(" %s = function (%s) {\n%s };\n\n return %1\$s();\n", $factory, $lazyloadInitialization, $c); + $c = sprintf(" %s = static function (\$container%s) {\n%s };\n\n return %1\$s(\$container);\n", $factory, $lazyloadInitialization, $c); } $code .= $c; } - if ($asFile) { - $code = str_replace('$this', '$container', $code); - $code = preg_replace('/function \(([^)]*+)\)( {|:)/', 'function (\1) use ($container)\2', $code); - } - $code .= " }\n"; $this->definitionVariables = $this->inlinedDefinitions = null; $this->referenceVariables = $this->serviceCalls = null; + $code = preg_replace('/%container_ref%/', $this->addContainerRef ? "\n \$containerRef = \$container->ref;\n" : '', $code, 1); + return [$file, $code]; } @@ -1014,8 +1013,8 @@ private function addInlineReference(string $id, Definition $definition, string $ $code .= sprintf(<<<'EOTXT' - if (isset($this->%s[%s])) { - return $this->%1$s[%2$s]; + if (isset($container->%s[%s])) { + return $container->%1$s[%2$s]; } EOTXT @@ -1190,7 +1189,7 @@ private function addNewInstance(Definition $definition, string $return = '', str if (\is_string($callable) && str_starts_with($callable, '@=')) { return $return.sprintf('(($args = %s) ? (%s) : null)', $this->dumpValue(new ServiceLocatorArgument($definition->getArguments())), - $this->getExpressionLanguage()->compile(substr($callable, 2), ['this' => 'container', 'args' => 'args']) + $this->getExpressionLanguage()->compile(substr($callable, 2), ['container' => 'container', 'args' => 'args']) ).$tail; } @@ -1234,9 +1233,11 @@ private function startClass(string $class, string $baseClass, bool $hasProxyClas class $class extends $baseClass { protected \$parameters = []; + protected readonly \WeakReference \$ref; public function __construct() { + \$this->ref = \WeakReference::create(\$this); EOF; if ($this->asFiles) { @@ -1470,11 +1471,11 @@ private function addDeprecatedAliases(): string * * @return object The "$id" service. */ - protected function {$methodNameAlias}() + protected static function {$methodNameAlias}(\$container) { trigger_deprecation($packageExported, $versionExported, $messageExported); - return \$this->get($idExported); + return \$container->get($idExported); } EOF; @@ -1517,7 +1518,7 @@ private function addInlineRequires(bool $hasProxyClasses): string $code .= "\n include_once __DIR__.'/proxy-classes.php';"; } - return $code ? sprintf("\n \$this->privates['service_container'] = function () {%s\n };\n", $code) : ''; + return $code ? sprintf("\n \$this->privates['service_container'] = static function (\$container) {%s\n };\n", $code) : ''; } private function addDefaultParametersMethod(): string @@ -1537,7 +1538,7 @@ private function addDefaultParametersMethod(): string $export = $this->exportParameters([$value], '', 12, $hasEnum); $export = explode('0 => ', substr(rtrim($export, " ]\n"), 2, -1), 2); - if ($hasEnum || preg_match("/\\\$this->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w++'\)|targetDir\.'')/", $export[1])) { + if ($hasEnum || preg_match("/\\\$container->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w++'\)|targetDir\.'')/", $export[1])) { $dynamicPhp[$key] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); } else { $php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); @@ -1601,6 +1602,7 @@ public function getParameterBag(): ParameterBagInterface if ($dynamicPhp) { $loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8); $getDynamicParameter = <<<'EOF' + $container = $this; $value = match ($name) { %s default => throw new ParameterNotFoundException($name), @@ -1694,14 +1696,14 @@ private function getServiceConditionals(mixed $value): string if (!$this->container->hasDefinition($service)) { return 'false'; } - $conditions[] = sprintf('isset($this->%s[%s])', $this->container->getDefinition($service)->isPublic() ? 'services' : 'privates', $this->doExport($service)); + $conditions[] = sprintf('isset($container->%s[%s])', $this->container->getDefinition($service)->isPublic() ? 'services' : 'privates', $this->doExport($service)); } foreach (ContainerBuilder::getServiceConditionals($value) as $service) { if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) { continue; } - $conditions[] = sprintf('$this->has(%s)', $this->doExport($service)); + $conditions[] = sprintf('$container->has(%s)', $this->doExport($service)); } if (!$conditions) { @@ -1789,20 +1791,25 @@ private function dumpValue(mixed $value, bool $interpolate = true): string $attribute = sprintf('#[\Closure(%s)] ', $attribute); } + $this->addContainerRef = true; - return sprintf("%sfunction ()%s {\n %s\n }", $attribute, $returnedType, $code); + return sprintf("%sstatic function () use (\$containerRef)%s {\n \$container = \$containerRef->get();\n\n %s\n }", $attribute, $returnedType, $code); } if ($value instanceof IteratorArgument) { $operands = [0]; $code = []; - $code[] = 'new RewindableGenerator(function () {'; if (!$values = $value->getValues()) { + $code[] = 'new RewindableGenerator(static function () {'; $code[] = ' return new \EmptyIterator();'; } else { + $this->addContainerRef = true; + $code[] = 'new RewindableGenerator(static function () use ($containerRef) {'; + $code[] = ' $container = $containerRef->get();'; + $code[] = ''; $countCode = []; - $countCode[] = 'function () {'; + $countCode[] = 'static function () use ($containerRef) {'; foreach ($values as $k => $v) { ($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0]; @@ -1814,6 +1821,8 @@ private function dumpValue(mixed $value, bool $interpolate = true): string } } + $countCode[] = ' $container = $containerRef->get();'; + $countCode[] = ''; $countCode[] = sprintf(' return %s;', implode(' + ', $operands)); $countCode[] = ' }'; } @@ -1850,7 +1859,7 @@ private function dumpValue(mixed $value, bool $interpolate = true): string } $this->addGetService = true; - return sprintf('new \%s($this->getService, [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : ''); + return sprintf('new \%s($container->getService, [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : ''); } } finally { [$this->definitionVariables, $this->referenceVariables] = $scope; @@ -1888,7 +1897,7 @@ private function dumpValue(mixed $value, bool $interpolate = true): string return $this->getServiceCall($id, $value); } elseif ($value instanceof Expression) { - return $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']); + return $this->getExpressionLanguage()->compile((string) $value, ['container' => 'container']); } elseif ($value instanceof Parameter) { return $this->dumpParameter($value); } elseif (true === $interpolate && \is_string($value)) { @@ -1945,12 +1954,12 @@ private function dumpParameter(string $name): string return $dumpedValue; } - if (!preg_match("/\\\$this->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w++'\)|targetDir\.'')/", $dumpedValue)) { - return sprintf('$this->parameters[%s]', $this->doExport($name)); + if (!preg_match("/\\\$container->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w++'\)|targetDir\.'')/", $dumpedValue)) { + return sprintf('$container->parameters[%s]', $this->doExport($name)); } } - return sprintf('$this->getParameter(%s)', $this->doExport($name)); + return sprintf('$container->getParameter(%s)', $this->doExport($name)); } private function getServiceCall(string $id, Reference $reference = null): string @@ -1960,12 +1969,12 @@ private function getServiceCall(string $id, Reference $reference = null): string } if ('service_container' === $id) { - return '$this'; + return '$container'; } if ($this->container->hasDefinition($id) && $definition = $this->container->getDefinition($id)) { if ($definition->isSynthetic()) { - $code = sprintf('$this->get(%s%s)', $this->doExport($id), null !== $reference ? ', '.$reference->getInvalidBehavior() : ''); + $code = sprintf('$container->get(%s%s)', $this->doExport($id), null !== $reference ? ', '.$reference->getInvalidBehavior() : ''); } elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { $code = 'null'; if (!$definition->isShared()) { @@ -1977,20 +1986,20 @@ private function getServiceCall(string $id, Reference $reference = null): string } $code = $this->addNewInstance($definition, '', $id); if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { - return sprintf('($this->%s[%s] ??= %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); + return sprintf('($container->%s[%s] ??= %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); } $code = "($code)"; } else { - $code = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) ? "\$this->load('%s')" : '$this->%s()'; + $code = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) ? "\$container->load('%s')" : 'self::%s($container)'; $code = sprintf($code, $this->generateMethodName($id)); if (!$definition->isShared()) { - $factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); - $code = sprintf('(isset(%s) ? %1$s() : %s)', $factory, $code); + $factory = sprintf('$container->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); + $code = sprintf('(isset(%s) ? %1$s($container) : %s)', $factory, $code); } } if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { - $code = sprintf('($this->%s[%s] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); + $code = sprintf('($container->%s[%s] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); } return $code; @@ -1999,12 +2008,12 @@ private function getServiceCall(string $id, Reference $reference = null): string return 'null'; } if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $reference->getInvalidBehavior()) { - $code = sprintf('$this->get(%s, ContainerInterface::NULL_ON_INVALID_REFERENCE)', $this->doExport($id)); + $code = sprintf('$container->get(%s, ContainerInterface::NULL_ON_INVALID_REFERENCE)', $this->doExport($id)); } else { - $code = sprintf('$this->get(%s)', $this->doExport($id)); + $code = sprintf('$container->get(%s)', $this->doExport($id)); } - return sprintf('($this->services[%s] ?? %s)', $this->doExport($id), $code); + return sprintf('($container->services[%s] ?? %s)', $this->doExport($id), $code); } /** @@ -2095,7 +2104,7 @@ private function getExpressionLanguage(): ExpressionLanguage return $this->getServiceCall($id); } - return sprintf('$this->get(%s)', $arg); + return sprintf('$container->get(%s)', $arg); }); if ($this->container->isTrackingResources()) { @@ -2147,13 +2156,13 @@ private function export(mixed $value): mixed $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : ''; } - $dirname = $this->asFiles ? '$this->containerDir' : '__DIR__'; + $dirname = $this->asFiles ? '$container->containerDir' : '__DIR__'; $offset = 2 + $this->targetDirMaxMatches - \count($matches); if (0 < $offset) { $dirname = sprintf('\dirname(__DIR__, %d)', $offset + (int) $this->asFiles); } elseif ($this->asFiles) { - $dirname = "\$this->targetDir.''"; // empty string concatenation on purpose + $dirname = "\$container->targetDir.''"; // empty string concatenation on purpose } if ($prefix || $suffix) { @@ -2179,21 +2188,13 @@ private function doExport(mixed $value, bool $resolveEnv = false): mixed } else { $export = var_export($value, true); } - if ($this->asFiles) { - if (str_contains($export, '$this')) { - $export = str_replace('$this', "$'.'this", $export); - } - if (str_contains($export, 'function () {')) { - $export = str_replace('function () {', "function ('.') {", $export); - } - } - if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('string:%s').'")) { + if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$container->getEnv('string:%s').'")) { $export = $resolvedExport; if (str_ends_with($export, ".''")) { $export = substr($export, 0, -3); if ("'" === $export[1]) { - $export = substr_replace($export, '', 18, 7); + $export = substr_replace($export, '', 23, 7); } } if ("'" === $export[1]) { diff --git a/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php b/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php index f476c5fe6e0d4..05028781a340d 100644 --- a/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php +++ b/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php @@ -40,19 +40,19 @@ public function getFunctions(): array { return [ new ExpressionFunction('service', $this->serviceCompiler ?? function ($arg) { - return sprintf('$this->get(%s)', $arg); + return sprintf('$container->get(%s)', $arg); }, function (array $variables, $value) { return $variables['container']->get($value); }), new ExpressionFunction('parameter', function ($arg) { - return sprintf('$this->getParameter(%s)', $arg); + return sprintf('$container->getParameter(%s)', $arg); }, function (array $variables, $value) { return $variables['container']->getParameter($value); }), new ExpressionFunction('env', function ($arg) { - return sprintf('$this->getEnv(%s)', $arg); + return sprintf('$container->getEnv(%s)', $arg); }, function (array $variables, $value) { if (!$this->getEnv) { throw new LogicException('You need to pass a getEnv closure to the expression langage provider to use the "env" function.'); diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php index eeef902b0f889..09426c7e4627e 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php @@ -69,7 +69,7 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ $instantiation = 'return'; if ($definition->isShared()) { - $instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); + $instantiation .= sprintf(' $container->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); } $asGhostObject = str_contains($factoryCode, '$proxy'); @@ -78,22 +78,18 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ if (!$asGhostObject) { return <<createProxy('$proxyClass', fn () => \\$proxyClass::createLazyProxy(fn () => $factoryCode)); + $instantiation \$container->createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyProxy(static fn () => $factoryCode)); } EOF; } - if (preg_match('/^\$this->\w++\(\$proxy\)$/', $factoryCode)) { - $factoryCode = substr_replace($factoryCode, '(...)', -8); - } else { - $factoryCode = sprintf('fn ($proxy) => %s', $factoryCode); - } + $factoryCode = sprintf('static fn ($proxy) => %s', $factoryCode); return <<createProxy('$proxyClass', fn () => \\$proxyClass::createLazyGhost($factoryCode)); + $instantiation \$container->createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyGhost($factoryCode)); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 6be39f17e0a6d..b6cf6678fd5e5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -1243,6 +1243,7 @@ public function testDumpHandlesEnumeration() %A private function getDynamicParameter(string $name) { + $container = $this; $value = match ($name) { 'unit_enum' => \Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAR, 'enum_array' => [ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/closure.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/closure.php index cb9a3805400be..41cae53e5dcc4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/closure.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/closure.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'closure' => 'getClosureService', @@ -48,8 +50,8 @@ public function getRemovedIds(): array * * @return \Closure */ - protected function getClosureService() + protected static function getClosureService($container) { - return $this->services['closure'] = (new \stdClass())->__invoke(...); + return $container->services['closure'] = (new \stdClass())->__invoke(...); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_constructor_without_arguments.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_constructor_without_arguments.php index 8bdda6b602b9b..3745b291db52f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_constructor_without_arguments.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_constructor_without_arguments.php @@ -17,9 +17,11 @@ class ProjectServiceContainer extends \Symfony\Component\DependencyInjection\Tests\Fixtures\Container\ConstructorWithoutArgumentsContainer { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); parent::__construct(); $this->parameterBag = null; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_mandatory_constructor_arguments.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_mandatory_constructor_arguments.php index 85b8bc6dae27d..482316c0ff1a0 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_mandatory_constructor_arguments.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_mandatory_constructor_arguments.php @@ -17,9 +17,11 @@ class ProjectServiceContainer extends \Symfony\Component\DependencyInjection\Tests\Fixtures\Container\ConstructorWithMandatoryArgumentsContainer { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->aliases = []; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_optional_constructor_arguments.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_optional_constructor_arguments.php index 6aeea149719b9..adb80f267dd0b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_optional_constructor_arguments.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_optional_constructor_arguments.php @@ -17,9 +17,11 @@ class ProjectServiceContainer extends \Symfony\Component\DependencyInjection\Tests\Fixtures\Container\ConstructorWithOptionalArgumentsContainer { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); parent::__construct(); $this->parameterBag = null; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_without_constructor.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_without_constructor.php index ee47766101427..ff3750b0847cb 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_without_constructor.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_without_constructor.php @@ -17,9 +17,11 @@ class ProjectServiceContainer extends \Symfony\Component\DependencyInjection\Tests\Fixtures\Container\NoConstructorContainer { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->aliases = []; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php index da000bf8e41e3..98ea8c666ba36 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php @@ -17,9 +17,11 @@ class Container extends \Symfony\Component\DependencyInjection\Dump\AbstractContainer { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->aliases = []; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1.php index 4cec1eb29b06e..a1398a57c976d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->aliases = []; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10.php index f391e0d81e6de..a98229eb58979 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -43,9 +45,9 @@ public function isCompiled(): bool * * @return \stdClass */ - protected function getTestService() + protected static function getTestService($container) { - return $this->services['test'] = new \stdClass(['only dot' => '.', 'concatenation as value' => '.\'\'.', 'concatenation from the start value' => '\'\'.', '.' => 'dot as a key', '.\'\'.' => 'concatenation as a key', '\'\'.' => 'concatenation from the start key', 'optimize concatenation' => 'string1-string2', 'optimize concatenation with empty string' => 'string1string2', 'optimize concatenation from the start' => 'start', 'optimize concatenation at the end' => 'end', 'new line' => 'string with '."\n".'new line']); + return $container->services['test'] = new \stdClass(['only dot' => '.', 'concatenation as value' => '.\'\'.', 'concatenation from the start value' => '\'\'.', '.' => 'dot as a key', '.\'\'.' => 'concatenation as a key', '\'\'.' => 'concatenation from the start key', 'optimize concatenation' => 'string1-string2', 'optimize concatenation with empty string' => 'string1string2', 'optimize concatenation from the start' => 'start', 'optimize concatenation at the end' => 'end', 'new line' => 'string with '."\n".'new line']); } public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10_as_files.txt index 60742f551a2e1..b7c31e8137aa4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10_as_files.txt @@ -27,9 +27,13 @@ class getClosureService extends ProjectServiceContainer */ public static function do($container, $lazyLoad = true) { + $containerRef = $container->ref; + $container->services['closure'] = $instance = new \stdClass(); - $instance->closures = [0 => #[\Closure(name: 'foo', class: 'FooClass')] function () use ($container): ?\stdClass { + $instance->closures = [0 => #[\Closure(name: 'foo', class: 'FooClass')] static function () use ($containerRef): ?\stdClass { + $container = $containerRef->get(); + return ($container->services['foo'] ?? null); }]; @@ -59,9 +63,11 @@ class ProjectServiceContainer extends Container protected $targetDir; protected $parameters = []; private $buildParameters; + protected readonly \WeakReference $ref; public function __construct(array $buildParameters = [], $containerDir = __DIR__) { + $this->ref = \WeakReference::create($this); $this->buildParameters = $buildParameters; $this->containerDir = $containerDir; $this->targetDir = \dirname($containerDir); @@ -113,9 +119,9 @@ class ProjectServiceContainer extends Container * * @return \FooClass */ - protected function getFooService() + protected static function getFooService($container) { - return $this->services['foo'] = new \FooClass(new \stdClass()); + return $container->services['foo'] = new \FooClass(new \stdClass()); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php index 18f89e9caf754..d2a31115d1187 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -43,9 +45,9 @@ public function isCompiled(): bool * * @return \stdClass */ - protected function getTestService() + protected static function getTestService($container) { - return $this->services['test'] = new \stdClass(('file://'.\dirname(__DIR__, 1)), [('file://'.\dirname(__DIR__, 1)) => (\dirname(__DIR__, 2).'/')]); + return $container->services['test'] = new \stdClass(('file://'.\dirname(__DIR__, 1)), [('file://'.\dirname(__DIR__, 1)) => (\dirname(__DIR__, 2).'/')]); } public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services13.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services13.php index 9af8e4cd09f71..de0550de9b79c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services13.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services13.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'bar' => 'getBarService', @@ -48,11 +50,11 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getBarService() + protected static function getBarService($container) { $a = new \stdClass(); - $a->add($this); + $a->add($container); - return $this->services['bar'] = new \stdClass($a); + return $container->services['bar'] = new \stdClass($a); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php index cc56de79dfea3..328a9180ac5ab 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -44,9 +46,9 @@ public function isCompiled(): bool * * @return object A %env(FOO)% instance */ - protected function getServiceFromAnonymousFactoryService() + protected static function getServiceFromAnonymousFactoryService($container) { - return $this->services['service_from_anonymous_factory'] = (new ${($_ = $this->getEnv('FOO')) && false ?: "_"}())->getInstance(); + return $container->services['service_from_anonymous_factory'] = (new ${($_ = $container->getEnv('FOO')) && false ?: "_"}())->getInstance(); } /** @@ -54,9 +56,9 @@ protected function getServiceFromAnonymousFactoryService() * * @return \Bar\FooClass */ - protected function getServiceWithMethodCallAndFactoryService() + protected static function getServiceWithMethodCallAndFactoryService($container) { - $this->services['service_with_method_call_and_factory'] = $instance = new \Bar\FooClass(); + $container->services['service_with_method_call_and_factory'] = $instance = new \Bar\FooClass(); $instance->setBar(\Bar\FooClass::getInstance()); @@ -105,8 +107,9 @@ public function getParameterBag(): ParameterBagInterface private function getDynamicParameter(string $name) { + $container = $this; $value = match ($name) { - 'foo' => $this->getEnv('FOO'), + 'foo' => $container->getEnv('FOO'), default => throw new ParameterNotFoundException($name), }; $this->loadedDynamicParameters[$name] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services24.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services24.php index 320bdc3897c5f..b2bfd9e0865af 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services24.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services24.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'foo' => 'getFooService', @@ -41,8 +43,8 @@ public function isCompiled(): bool * * @return \Foo */ - protected function getFooService() + protected static function getFooService($container) { - return $this->services['foo'] = new \Foo(); + return $container->services['foo'] = new \Foo(); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php b/src/Symfony/Component 2851 /DependencyInjection/Tests/Fixtures/php/services26.php index 29820767b8bf6..ad424a8e467a6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_EnvParameters extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -44,9 +46,9 @@ public function isCompiled(): bool * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\Bar */ - protected function getBarService() + protected static function getBarService($container) { - return $this->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\Bar($this->getEnv('QUZ')); + return $container->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\Bar($container->getEnv('QUZ')); } /** @@ -54,9 +56,9 @@ protected function getBarService() * * @return object A %env(FOO)% instance */ - protected function getTestService() + protected static function getTestService($container) { - return $this->services['test'] = new ${($_ = $this->getEnv('FOO')) && false ?: "_"}($this->getEnv('Bar'), 'foo'.$this->getEnv('string:FOO').'baz', $this->getEnv('int:Baz')); + return $container->services['test'] = new ${($_ = $container->getEnv('FOO')) && false ?: "_"}($container->getEnv('Bar'), 'foo'.$container->getEnv('string:FOO').'baz', $container->getEnv('int:Baz')); } public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null @@ -104,11 +106,12 @@ public function getParameterBag(): ParameterBagInterface private function getDynamicParameter(string $name) { + $container = $this; $value = match ($name) { - 'bar' => $this->getEnv('FOO'), - 'baz' => $this->getEnv('int:Baz'), - 'json' => $this->getEnv('json:file:json_file'), - 'db_dsn' => $this->getEnv('resolve:DB'), + 'bar' => $container->getEnv('FOO'), + 'baz' => $container->getEnv('int:Baz'), + 'json' => $container->getEnv('json:file:json_file'), + 'db_dsn' => $container->getEnv('resolve:DB'), default => throw new ParameterNotFoundException($name), }; $this->loadedDynamicParameters[$name] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services33.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services33.php index 2e8c166d16ac4..eed5f3f6270dd 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services33.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services33.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'Bar\\Foo' => 'getFooService', @@ -42,9 +44,9 @@ public function isCompiled(): bool * * @return \Bar\Foo */ - protected function getFooService() + protected static function getFooService($container) { - return $this->services['Bar\\Foo'] = new \Bar\Foo(); + return $container->services['Bar\\Foo'] = new \Bar\Foo(); } /** @@ -52,8 +54,8 @@ protected function getFooService() * * @return \Foo\Foo */ - protected function getFoo2Service() + protected static function getFoo2Service($container) { - return $this->services['Foo\\Foo'] = new \Foo\Foo(); + return $container->services['Foo\\Foo'] = new \Foo\Foo(); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php index 88dcb00075cb5..b5d86ce86b753 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt index d4ef329a1ad29..1321a66ec1bce 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt @@ -39,7 +39,7 @@ class getBAR2Service extends ProjectServiceContainer { $container->services['BAR'] = $instance = new \stdClass(); - $instance->bar = ($container->services['bar'] ?? $container->getBarService()); + $instance->bar = ($container->services['bar'] ?? self::getBarService($container)); return $instance; } @@ -281,7 +281,7 @@ class getFooService extends ProjectServiceContainer $instance->foo = 'bar'; $instance->moo = $a; $instance->qux = ['bar' => 'foo is bar', 'foobar' => 'bar']; - $instance->setBar(($container->services['bar'] ?? $container->getBarService())); + $instance->setBar(($container->services['bar'] ?? self::getBarService($container))); $instance->initialize(); sc_configure($instance); @@ -319,11 +319,11 @@ class getFooBarService extends ProjectServiceContainer */ public static function do($container, $lazyLoad = true) { - $container->factories['foo_bar'] = function () use ($container) { + $container->factories['foo_bar'] = static function ($container) { return new \Bar\FooClass(($container->services['deprecated_service'] ?? $container->load('getDeprecatedServiceService'))); }; - return $container->factories['foo_bar'](); + return $container->factories['foo_bar']($container); } } @@ -361,10 +361,14 @@ class getLazyContextService extends ProjectServiceContainer */ public static function do($container, $lazyLoad = true) { - return $container->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function () use ($container) { + $containerRef = $container->ref; + + return $container->services['lazy_context'] = new \LazyContext(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + yield 'k1' => ($container->services['foo.baz'] ?? $container->load('getFoo_BazService')); yield 'k2' => $container; - }, 2), new RewindableGenerator(function () use ($container) { + }, 2), new RewindableGenerator(static function () { return new \EmptyIterator(); }, 0)); } @@ -381,9 +385,13 @@ class getLazyContextIgnoreInvalidRefService extends ProjectServiceContainer */ public static function do($container, $lazyLoad = true) { - return $container->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function () use ($container) { + $containerRef = $container->ref; + + return $container->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + yield 0 => ($container->services['foo.baz'] ?? $container->load('getFoo_BazService')); - }, 1), new RewindableGenerator(function () use ($container) { + }, 1), new RewindableGenerator(static function () { return new \EmptyIterator(); }, 0)); } @@ -447,11 +455,11 @@ class getNonSharedFooService extends ProjectServiceContainer { include_once $container->targetDir.''.'/Fixtures/includes/foo.php'; - $container->factories['non_shared_foo'] = function () use ($container) { + $container->factories['non_shared_foo'] = static function ($container) { return new \Bar\FooClass(); }; - return $container->factories['non_shared_foo'](); + return $container->factories['non_shared_foo']($container); } } @@ -511,7 +519,11 @@ class getTaggedIteratorService extends ProjectServiceContainer */ public static function do($container, $lazyLoad = true) { - return $container->services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () use ($container) { + $containerRef = $container->ref; + + return $container->services['tagged_iterator'] = new \Bar(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + yield 0 => ($container->services['foo'] ?? $container->load('getFooService')); yield 1 => ($container->privates['tagged_iterator_foo'] ??= new \Bar()); }, 2)); @@ -555,9 +567,11 @@ class ProjectServiceContainer extends Container protected $targetDir; protected $parameters = []; private $buildParameters; + protected readonly \WeakReference $ref; public function __construct(array $buildParameters = [], $containerDir = __DIR__) { + $this->ref = \WeakReference::create($this); $this->buildParameters = $buildParameters; $this->containerDir = $containerDir; $this->targetDir = \dirname($containerDir); @@ -643,11 +657,11 @@ class ProjectServiceContainer extends Container * * @return \Bar\FooClass */ - protected function getBarService() + protected static function getBarService($container) { - $a = ($this->se F438 rvices['foo.baz'] ?? $this->load('getFoo_BazService')); + $a = ($container->services['foo.baz'] ?? $container->load('getFoo_BazService')); - $this->services['bar'] = $instance = new \Bar\FooClass('foo', $a, $this->getParameter('foo_bar')); + $container->services['bar'] = $instance = new \Bar\FooClass('foo', $a, $container->getParameter('foo_bar')); $a->configure($instance); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index 4c4d4b2fbb06b..7b4c50ffb94d7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -90,11 +92,11 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getBARService() + protected static function getBARService($container) { - $this->services['BAR'] = $instance = new \stdClass(); + $container->services['BAR'] = $instance = new \stdClass(); - $instance->bar = ($this->services['bar'] ?? $this->getBar3Service()); + $instance->bar = ($container->services['bar'] ?? self::getBar3Service($container)); return $instance; } @@ -104,9 +106,9 @@ protected function getBARService() * * @return \stdClass */ - protected function getBAR2Service() + protected static function getBAR2Service($container) { - return $this->services['BAR2'] = new \stdClass(); + return $container->services['BAR2'] = new \stdClass(); } /** @@ -114,9 +116,9 @@ protected function getBAR2Service() * * @return \Bar */ - protected function getAServiceService() + protected static function getAServiceService($container) { - return $this->services['a_service'] = ($this->privates['a_factory'] ??= new \Bar())->getBar(); + return $container->services['a_service'] = ($container->privates['a_factory'] ??= new \Bar())->getBar(); } /** @@ -124,9 +126,9 @@ protected function getAServiceService() * * @return \Bar */ - protected function getBServiceService() + protected static function getBServiceService($container) { - return $this->services['b_service'] = ($this->privates['a_factory'] ??= new \Bar())->getBar(); + return $container->services['b_service'] = ($container->privates['a_factory'] ??= new \Bar())->getBar(); } /** @@ -134,11 +136,11 @@ protected function getBServiceService() * * @return \Bar\FooClass */ - protected function getBar3Service() + protected static function getBar3Service($container) { - $a = ($this->services['foo.baz'] ?? $this->getFoo_BazService()); + $a = ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); - $this->services['bar'] = $instance = new \Bar\FooClass('foo', $a, $this->getParameter('foo_bar')); + $container->services['bar'] = $instance = new \Bar\FooClass('foo', $a, $container->getParameter('foo_bar')); $a->configure($instance); @@ -150,9 +152,9 @@ protected function getBar3Service() * * @return \stdClass */ - protected function getBar22Service() + protected static function getBar22Service($container) { - return $this->services['bar2'] = new \stdClass(); + return $container->services['bar2'] = new \stdClass(); } /** @@ -160,11 +162,11 @@ protected function getBar22Service() * * @return \Baz */ - protected function getBazService() + protected static function getBazService($container) { - $this->services['baz'] = $instance = new \Baz(); + $container->services['baz'] = $instance = new \Baz(); - $instance->setFoo(($this->services['foo_with_inline'] ?? $this->getFooWithInlineService())); + $instance->setFoo(($container->services['foo_with_inline'] ?? self::getFooWithInlineService($container))); return $instance; } @@ -174,12 +176,12 @@ protected function getBazService() * * @return \stdClass */ - protected function getConfiguredServiceService() + protected static function getConfiguredServiceService($container) { - $this->services['configured_service'] = $instance = new \stdClass(); + $container->services['configured_service'] = $instance = new \stdClass(); $a = new \ConfClass(); - $a->setFoo(($this->services['baz'] ?? $this->getBazService())); + $a->setFoo(($container->services['baz'] ?? self::getBazService($container))); $a->configureStdClass($instance); @@ -191,9 +193,9 @@ protected function getConfiguredServiceService() * * @return \stdClass */ - protected function getConfiguredServiceSimpleService() + protected static function getConfiguredServiceSimpleService($container) { - $this->services['configured_service_simple'] = $instance = new \stdClass(); + $container->services['configured_service_simple'] = $instance = new \stdClass(); (new \ConfClass('bar'))->configureStdClass($instance); @@ -205,9 +207,9 @@ protected function getConfiguredServiceSimpleService() * * @return \stdClass */ - protected function getDecoratorServiceService() + protected static function getDecoratorServiceService($container) { - return $this->services['decorator_service'] = new \stdClass(); + return $container->services['decorator_service'] = new \stdClass(); } /** @@ -215,9 +217,9 @@ protected function getDecoratorServiceService() * * @return \stdClass */ - protected function getDecoratorServiceWithNameService() + protected static function getDecoratorServiceWithNameService($container) { - return $this->services['decorator_service_with_name'] = new \stdClass(); + return $container->services['decorator_service_with_name'] = new \stdClass(); } /** @@ -227,11 +229,11 @@ protected function getDecoratorServiceWithNameService() * * @deprecated Since vendor/package 1.1: The "deprecated_service" service is deprecated. You should stop using it, as it will be removed in the future. */ - protected function getDeprecatedServiceService() + protected static function getDeprecatedServiceService($container) { trigger_deprecation('vendor/package', '1.1', 'The "deprecated_service" service is deprecated. You should stop using it, as it will be removed in the future.'); - return $this->services['deprecated_service'] = new \stdClass(); + return $container->services['deprecated_service'] = new \stdClass(); } /** @@ -239,9 +241,9 @@ protected function getDeprecatedServiceService() * * @return \Bar */ - protected function getFactoryServiceService() + protected static function getFactoryServiceService($container) { - return $this->services['factory_service'] = ($this->services['foo.baz'] ?? $this->getFoo_BazService())->getInstance(); + return $container->services['factory_service'] = ($container->services['foo.baz'] ?? self::getFoo_BazService($container))->getInstance(); } /** @@ -249,9 +251,9 @@ protected function getFactoryServiceService() * * @return \Bar */ - protected function getFactoryServiceSimpleService() + protected static function getFactoryServiceSimpleService($container) { - return $this->services['factory_service_simple'] = $this->getFactorySimpleService()->getInstance(); + return $container->services['factory_service_simple'] = self::getFactorySimpleService($container)->getInstance(); } /** @@ -259,16 +261,16 @@ protected function getFactoryServiceSimpleService() * * @return \Bar\FooClass */ - protected function getFooService() + protected static function getFooService($container) { - $a = ($this->services['foo.baz'] ?? $this->getFoo_BazService()); + $a = ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); - $this->services['foo'] = $instance = \Bar\FooClass::getInstance('foo', $a, ['bar' => 'foo is bar', 'foobar' => 'bar'], true, $this); + $container->services['foo'] = $instance = \Bar\FooClass::getInstance('foo', $a, ['bar' => 'foo is bar', 'foobar' => 'bar'], true, $container); $instance->foo = 'bar'; $instance->moo = $a; $instance->qux = ['bar' => 'foo is bar', 'foobar' => 'bar']; - $instance->setBar(($this->services['bar'] ?? $this->getBar3Service())); + $instance->setBar(($container->services['bar'] ?? self::getBar3Service($container))); $instance->initialize(); sc_configure($instance); @@ -280,9 +282,9 @@ protected function getFooService() * * @return \BazClass */ - protected function getFoo_BazService() + protected static function getFoo_BazService($container) { - $this->services['foo.baz'] = $instance = \BazClass::getInstance(); + $container->services['foo.baz'] = $instance = \BazClass::getInstance(); \BazClass::configureStatic1($instance); @@ -294,13 +296,13 @@ protected function getFoo_BazService() * * @return \Bar\FooClass */ - protected function getFooBarService() + protected static function getFooBarService($container) { - $this->factories['foo_bar'] = function () { - return new \Bar\FooClass(($this->services['deprecated_service'] ?? $this->getDeprecatedServiceService())); + $container->factories['foo_bar'] = static function ($container) { + return new \Bar\FooClass(($container->services['deprecated_service'] ?? self::getDeprecatedServiceService($container))); }; - return $this->factories['foo_bar'](); + return $container->factories['foo_bar']($container); } /** @@ -308,13 +310,13 @@ protected function getFooBarService() * * @return \Foo */ - protected function getFooWithInlineService() + protected static function getFooWithInlineService($container) { - $this->services['foo_with_inline'] = $instance = new \Foo(); + $container->services['foo_with_inline'] = $instance = new \Foo(); $a = new \Bar(); $a->pub = 'pub'; - $a->setBaz(($this->services['baz'] ?? $this->getBazService())); + $a->setBaz(($container->services['baz'] ?? self::getBazService($container))); $instance->setBar($a); @@ -326,12 +328,16 @@ protected function getFooWithInlineService() * * @return \LazyContext */ - protected function getLazyContextService() + protected static function getLazyContextService($container) { - return $this->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function () { - yield 'k1' => ($this->services['foo.baz'] ?? $this->getFoo_BazService()); - yield 'k2' => $this; - }, 2), new RewindableGenerator(function () { + $containerRef = $container->ref; + + return $container->services['lazy_context'] = new \LazyContext(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 'k1' => ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); + yield 'k2' => $container; + }, 2), new RewindableGenerator(static function () { return new \EmptyIterator(); }, 0)); } @@ -341,11 +347,15 @@ protected function getLazyContextService() * * @return \LazyContext */ - protected function getLazyContextIgnoreInvalidRefService() + protected static function getLazyContextIgnoreInvalidRefService($container) { - return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function () { - yield 0 => ($this->services['foo.baz'] ?? $this->getFoo_BazService()); - }, 1), new RewindableGenerator(function () { + $containerRef = $container->ref; + + return $container->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 0 => ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); + }, 1), new RewindableGenerator(static function () { return new \EmptyIterator(); }, 0)); } @@ -355,15 +365,15 @@ protected function getLazyContextIgnoreInvalidRefService() * * @return \Bar\FooClass */ - protected function getMethodCall1Service() + protected static function getMethodCall1Service($container) { include_once '%path%foo.php'; - $this->services['method_call1'] = $instance = new \Bar\FooClass(); + $container->services['method_call1'] = $instance = new \Bar\FooClass(); - $instance->setBar(($this->services['foo'] ?? $this->getFooService())); + $instance->setBar(($container->services['foo'] ?? self::getFooService($container))); $instance->setBar(NULL); - $instance->setBar((($this->services['foo'] ?? $this->getFooService())->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default")))); + $instance->setBar((($container->services['foo'] ?? self::getFooService($container))->foo() . (($container->hasParameter("foo")) ? ($container->getParameter("foo")) : ("default")))); return $instance; } @@ -373,12 +383,12 @@ protected function getMethodCall1Service() * * @return \FooBarBaz */ - protected function getNewFactoryServiceService() + protected static function getNewFactoryServiceService($container) { $a = new \FactoryClass(); $a->foo = 'bar'; - $this->services['new_factory_service'] = $instance = $a->getInstance(); + $container->services['new_factory_service'] = $instance = $a->getInstance(); $instance->foo = 'bar'; @@ -390,9 +400,9 @@ protected function getNewFactoryServiceService() * * @return \stdClass */ - protected function getPreloadSidekickService() + protected static function getPreloadSidekickService($container) { - return $this->services['preload_sidekick'] = new \stdClass(); + return $container->services['preload_sidekick'] = new \stdClass(); } /** @@ -400,9 +410,9 @@ protected function getPreloadSidekickService() * * @return \stdClass */ - protected function getRuntimeErrorService() + protected static function getRuntimeErrorService($container) { - return $this->services['runtime_error'] = new \stdClass(throw new RuntimeException('Service "errored_definition" is broken.')); + return $container->services['runtime_error'] = new \stdClass(throw new RuntimeException('Service "errored_definition" is broken.')); } /** @@ -410,9 +420,9 @@ protected function getRuntimeErrorService() * * @return \Bar\FooClass */ - protected function getServiceFromStaticMethodService() + protected static function getServiceFromStaticMethodService($container) { - return $this->services['service_from_static_method'] = \Bar\FooClass::getInstance(); + return $container->services['service_from_static_method'] = \Bar\FooClass::getInstance(); } /** @@ -420,11 +430,15 @@ protected function getServiceFromStaticMethodService() * * @return \Bar */ - protected function getTaggedIteratorService() + protected static function getTaggedIteratorService($container) { - return $this->services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () { - yield 0 => ($this->services['foo'] ?? $this->getFooService()); - yield 1 => ($this->privates['tagged_iterator_foo'] ??= new \Bar()); + $containerRef = $container->ref; + + return $container->services['tagged_iterator'] = new \Bar(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 0 => ($container->services['foo'] ?? self::getFooService($container)); + yield 1 => ($container->privates['tagged_iterator_foo'] ??= new \Bar()); }, 2)); } @@ -435,7 +449,7 @@ protected function getTaggedIteratorService() * * @deprecated Since vendor/package 1.1: The "factory_simple" service is deprecated. You should stop using it, as it will be removed in the future. */ - protected function getFactorySimpleService() + protected static function getFactorySimpleService($container) { trigger_deprecation('vendor/package', '1.1', 'The "factory_simple" service is deprecated. You should stop using it, as it will be removed in the future.'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt index 137506abdf4b0..920e4036507a4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt @@ -40,9 +40,11 @@ class ProjectServiceContainer extends Container protected $targetDir; protected $parameters = []; private $buildParameters; + protected readonly \WeakReference $ref; public function __construct(array $buildParameters = [], $containerDir = __DIR__) { + $this->ref = \WeakReference::create($this); $this->buildParameters = $buildParameters; $this->containerDir = $containerDir; $this->targetDir = \dirname($containerDir); @@ -88,8 +90,8 @@ class ProjectServiceContainer extends Container 'decorated' => 'decorator_service_with_name', ]; - $this->privates['service_container'] = function () { - include_once $this->targetDir.''.'/Fixtures/includes/foo.php'; + $this->privates['service_container'] = static function ($container) { + include_once $container->targetDir.''.'/Fixtures/includes/foo.php'; }; } @@ -113,11 +115,11 @@ class ProjectServiceContainer extends Container * * @return \stdClass */ - protected function getBARService() + protected static function getBARService($container) { - $this->services['BAR'] = $instance = new \stdClass(); + $container->services['BAR'] = $instance = new \stdClass(); - $instance->bar = ($this->services['bar'] ?? $this->getBar3Service()); + $instance->bar = ($container->services['bar'] ?? self::getBar3Service($container)); return $instance; } @@ -127,9 +129,9 @@ class ProjectServiceContainer extends Container * * @return \stdClass */ - protected function getBAR2Service() + protected static function getBAR2Service($container) { - return $this->services['BAR2'] = new \stdClass(); + return $container->services['BAR2'] = new \stdClass(); } /** @@ -137,9 +139,9 @@ class ProjectServiceContainer extends Container * * @return \Bar */ - protected function getAServiceService() + protected static function getAServiceService($container) { - return $this->services['a_service'] = ($this->privates['a_factory'] ??= new \Bar())->getBar(); + return $container->services['a_service'] = ($container->privates['a_factory'] ??= new \Bar())->getBar(); } /** @@ -147,9 +149,9 @@ class ProjectServiceContainer extends Container * * @return \Bar */ - protected function getBServiceService() + protected static function getBServiceService($container) { - return $this->services['b_service'] = ($this->privates['a_factory'] ??= new \Bar())->getBar(); + return $container->services['b_service'] = ($container->privates['a_factory'] ??= new \Bar())->getBar(); } /** @@ -157,11 +159,11 @@ class ProjectServiceContainer extends Container * * @return \Bar\FooClass */ - protected function getBar3Service() + protected static function getBar3Service($container) { - $a = ($this->services['foo.baz'] ?? $this->getFoo_BazService()); + $a = ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); - $this->services['bar'] = $instance = new \Bar\FooClass('foo', $a, $this->getParameter('foo_bar')); + $container->services['bar'] = $instance = new \Bar\FooClass('foo', $a, $container->getParameter('foo_bar')); $a->configure($instance); @@ -173,9 +175,9 @@ class ProjectServiceContainer extends Container * * @return \stdClass */ - protected function getBar22Service() + protected static function getBar22Service($container) { - return $this->services['bar2'] = new \stdClass(); + return $container->services['bar2'] = new \stdClass(); } /** @@ -183,11 +185,11 @@ class ProjectServiceContainer extends Container * * @return \Baz */ - protected function getBazService() + protected static function getBazService($container) { - $this->services['baz'] = $instance = new \Baz(); + $container->services['baz'] = $instance = new \Baz(); - $instance->setFoo(($this->services['foo_with_inline'] ?? $this->getFooWithInlineService())); + $instance->setFoo(($container->services['foo_with_inline'] ?? self::getFooWithInlineService($container))); return $instance; } @@ -197,12 +199,12 @@ class ProjectServiceContainer extends Container * * @return \stdClass */ - protected function getConfiguredServiceService() + protected static function getConfiguredServiceService($container) { - $this->services['configured_service'] = $instance = new \stdClass(); + $container->services['configured_service'] = $instance = new \stdClass(); $a = new \ConfClass(); - $a->setFoo(($this->services['baz'] ?? $this->getBazService())); + $a->setFoo(($container->services['baz'] ?? self::getBazService($container))); $a->configureStdClass($instance); @@ -214,9 +216,9 @@ class ProjectServiceContainer extends Container * * @return \stdClass */ - protected function getConfiguredServiceSimpleService() + protected static function getConfiguredServiceSimpleService($container) { - $this->services['configured_service_simple'] = $instance = new \stdClass(); + $container->services['configured_service_simple'] = $instance = new \stdClass(); (new \ConfClass('bar'))->configureStdClass($instance); @@ -228,9 +230,9 @@ class ProjectServiceContainer extends Container * * @return \stdClass */ - protected function getDecoratorServiceService() + protected static function getDecoratorServiceService($container) { - return $this->services['decorator_service'] = new \stdClass(); + return $container->services['decorator_service'] = new \stdClass(); } /** @@ -238,9 +240,9 @@ class ProjectServiceContainer extends Container * * @return \stdClass */ - protected function getDecoratorServiceWithNameService() + protected static function getDecoratorServiceWithNameService($container) { - return $this->services['decorator_service_with_name'] = new \stdClass(); + return $container->services['decorator_service_with_name'] = new \stdClass(); } /** @@ -250,11 +252,11 @@ class ProjectServiceContainer extends Container * * @deprecated Since vendor/package 1.1: The "deprecated_service" service is deprecated. You should stop using it, as it will be removed in the future. */ - protected function getDeprecatedServiceService() + protected static function getDeprecatedServiceService($container) { trigger_deprecation('vendor/package', '1.1', 'The "deprecated_service" service is deprecated. You should stop using it, as it will be removed in the future.'); - return $this->services['deprecated_service'] = new \stdClass(); + return $container->services['deprecated_service'] = new \stdClass(); } /** @@ -262,9 +264,9 @@ class ProjectServiceContainer extends Container * * @return \Bar */ - protected function getFactoryServiceService() + protected static function getFactoryServiceService($container) { - return $this->services['factory_service'] = ($this->services['foo.baz'] ?? $this->getFoo_BazService())->getInstance(); + return $container->services['factory_service'] = ($container->services['foo.baz'] ?? self::getFoo_BazService($container))->getInstance(); } /** @@ -272,9 +274,9 @@ class ProjectServiceContainer extends Container * * @return \Bar */ - protected function getFactoryServiceSimpleService() + protected static function getFactoryServiceSimpleService($container) { - return $this->services['factory_service_simple'] = $this->getFactorySimpleService()->getInstance(); + return $container->services['factory_service_simple'] = self::getFactorySimpleService($container)->getInstance(); } /** @@ -282,16 +284,16 @@ class ProjectServiceContainer extends Container * * @return \Bar\FooClass */ - protected function getFooService() + protected static function getFooService($container) { - $a = ($this->services['foo.baz'] ?? $this->getFoo_BazService()); + $a = ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); - $this->services['foo'] = $instance = \Bar\FooClass::getInstance('foo', $a, ['bar' => 'foo is bar', 'foobar' => 'bar'], true, $this); + $container->services['foo'] = $instance = \Bar\FooClass::getInstance('foo', $a, ['bar' => 'foo is bar', 'foobar' => 'bar'], true, $container); $instance->foo = 'bar'; $instance->moo = $a; $instance->qux = ['bar' => 'foo is bar', 'foobar' => 'bar']; - $instance->setBar(($this->services['bar'] ?? $this->getBar3Service())); + $instance->setBar(($container->services['bar'] ?? self::getBar3Service($container))); $instance->initialize(); sc_configure($instance); @@ -303,11 +305,11 @@ class ProjectServiceContainer extends Container * * @return \BazClass */ - protected function getFoo_BazService() + protected static function getFoo_BazService($container) { - include_once $this->targetDir.''.'/Fixtures/includes/classes.php'; + include_once $container->targetDir.''.'/Fixtures/includes/classes.php'; - $this->services['foo.baz'] = $instance = \BazClass::getInstance(); + $container->services['foo.baz'] = $instance = \BazClass::getInstance(); \BazClass::configureStatic1($instance); @@ -319,13 +321,13 @@ class ProjectServiceContainer extends Container * * @return \Bar\FooClass */ - protected function getFooBarService() + protected static function getFooBarService($container) { - $this->factories['foo_bar'] = function () { - return new \Bar\FooClass(($this->services['deprecated_service'] ?? $this->getDeprecatedServiceService())); + $container->factories['foo_bar'] = static function ($container) { + return new \Bar\FooClass(($container->services['deprecated_service'] ?? self::getDeprecatedServiceService($container))); }; - return $this->factories['foo_bar'](); + return $container->factories['foo_bar']($container); } /** @@ -333,13 +335,13 @@ class ProjectServiceContainer extends Container * * @return \Foo */ - protected function getFooWithInlineService() + protected static function getFooWithInlineService($container) { - $this->services['foo_with_inline'] = $instance = new \Foo(); + $container->services['foo_with_inline'] = $instance = new \Foo(); $a = new \Bar(); $a->pub = 'pub'; - $a->setBaz(($this->services['baz'] ?? $this->getBazService())); + $a->setBaz(($container->services['baz'] ?? self::getBazService($container))); $instance->setBar($a); @@ -351,14 +353,18 @@ class ProjectServiceContainer extends Container * * @return \LazyContext */ - protected function getLazyContextService() + protected static function getLazyContextService($container) { - include_once $this->targetDir.''.'/Fixtures/includes/classes.php'; + $containerRef = $container->ref; - return $this->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function () { - yield 'k1' => ($this->services['foo.baz'] ?? $this->getFoo_BazService()); - yield 'k2' => $this; - }, 2), new RewindableGenerator(function () { + include_once $container->targetDir.''.'/Fixtures/includes/classes.php'; + + return $container->services['lazy_context'] = new \LazyContext(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 'k1' => ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); + yield 'k2' => $container; + }, 2), new RewindableGenerator(static function () { return new \EmptyIterator(); }, 0)); } @@ -368,13 +374,17 @@ class ProjectServiceContainer extends Container * * @return \LazyContext */ - protected function getLazyContextIgnoreInvalidRefService() + protected static function getLazyContextIgnoreInvalidRefService($container) { - include_once $this->targetDir.''.'/Fixtures/includes/classes.php'; + $containerRef = $container->ref; + + include_once $container->targetDir.''.'/Fixtures/includes/classes.php'; - return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function () { - yield 0 => ($this->services['foo.baz'] ?? $this->getFoo_BazService()); - }, 1), new RewindableGenerator(function () { + return $container->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 0 => ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); + }, 1), new RewindableGenerator(static function () { return new \EmptyIterator(); }, 0)); } @@ -384,15 +394,15 @@ class ProjectServiceContainer extends Container * * @return \Bar\FooClass */ - protected function getMethodCall1Service() + protected static function getMethodCall1Service($container) { - include_once $this->targetDir.''.'/Fixtures/includes/foo.php'; + include_once $container->targetDir.''.'/Fixtures/includes/foo.php'; - $this->services['method_call1'] = $instance = new \Bar\FooClass(); + $container->services['method_call1'] = $instance = new \Bar\FooClass(); - $instance->setBar(($this->services['foo'] ?? $this->getFooService())); + $instance->setBar(($container->services['foo'] ?? self::getFooService($container))); $instance->setBar(NULL); - $instance->setBar((($this->services['foo'] ?? $this->getFooService())->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default")))); + $instance->setBar((($container->services['foo'] ?? self::getFooService($container))->foo() . (($container->hasParameter("foo")) ? ($container->getParameter("foo")) : ("default")))); return $instance; } @@ -402,12 +412,12 @@ class ProjectServiceContainer extends Container * * @return \FooBarBaz */ - protected function getNewFactoryServiceService() + protected static function getNewFactoryServiceService($container) { $a = new \FactoryClass(); $a->foo = 'bar'; - $this->services['new_factory_service'] = $instance = $a->getInstance(); + $container->services['new_factory_service'] = $instance = $a->getInstance(); $instance->foo = 'bar'; @@ -419,15 +429,15 @@ class ProjectServiceContainer extends Container * * @return \Bar\FooClass */ - protected function getNonSharedFooService() + protected static function getNonSharedFooService($container) { - include_once $this->targetDir.''.'/Fixtures/includes/foo.php'; + include_once $container->targetDir.''.'/Fixtures/includes/foo.php'; - $this->factories['non_shared_foo'] = function () { + $container->factories['non_shared_foo'] = static function ($container) { return new \Bar\FooClass(); }; - return $this->factories['non_shared_foo'](); + return $container->factories['non_shared_foo']($container); } /** @@ -435,9 +445,9 @@ class ProjectServiceContainer extends Container * * @return \stdClass */ - protected function getPreloadSidekickService() + protected static function getPreloadSidekickService($container) { - return $this->services['preload_sidekick'] = new \stdClass(); + return $container->services['preload_sidekick'] = new \stdClass(); } /** @@ -445,9 +455,9 @@ class ProjectServiceContainer extends Container * * @return \stdClass */ - protected function getRuntimeErrorService() + protected static function getRuntimeErrorService($container) { - return $this->services['runtime_error'] = new \stdClass(throw new RuntimeException('Service "errored_definition" is broken.')); + return $container->services['runtime_error'] = new \stdClass(throw new RuntimeException('Service "errored_definition" is broken.')); } /** @@ -455,9 +465,9 @@ class ProjectServiceContainer extends Container * * @return \Bar\FooClass */ - protected function getServiceFromStaticMethodService() + protected static function getServiceFromStaticMethodService($container) { - return $this->services['service_from_static_method'] = \Bar\FooClass::getInstance(); + return $container->services['service_from_static_method'] = \Bar\FooClass::getInstance(); } /** @@ -465,11 +475,15 @@ class ProjectServiceContainer extends Container * * @return \Bar */ - protected function getTaggedIteratorService() + protected static function getTaggedIteratorService($container) { - return $this->services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () { - yield 0 => ($this->services['foo'] ?? $this->getFooService()); - yield 1 => ($this->privates['tagged_iterator_foo'] ??= new \Bar()); + $containerRef = $container->ref; + + return $container->services['tagged_iterator'] = new \Bar(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 0 => ($container->services['foo'] ?? self::getFooService($container)); + yield 1 => ($container->privates['tagged_iterator_foo'] ??= new \Bar()); }, 2)); } @@ -478,9 +492,9 @@ class ProjectServiceContainer extends Container * * @return \Bar\FooClass */ - protected function getThrowingOneService() + protected static function getThrowingOneService($container) { - return $this->services['throwing_one'] = new \Bar\FooClass(throw new RuntimeException('No-no-no-no')); + return $container->services['throwing_one'] = new \Bar\FooClass(throw new RuntimeException('No-no-no-no')); } /** @@ -490,7 +504,7 @@ class ProjectServiceContainer extends Container * * @deprecated Since vendor/package 1.1: The "factory_simple" service is deprecated. You should stop using it, as it will be removed in the future. */ - protected function getFactorySimpleService() + protected static function getFactorySimpleService($container) { trigger_deprecation('vendor/package', '1.1', 'The "factory_simple" service is deprecated. You should stop using it, as it will be removed in the future.'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt index b1bf8826b43dd..31cf95ed38123 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt @@ -4,7 +4,7 @@ Array namespace Container%s; -include_once $this->targetDir.''.'/Fixtures/includes/foo.php'; +include_once $container->targetDir.''.'/Fixtures/includes/foo.php'; class FooClassGhost2b16075 extends \Bar\FooClass implements \Symfony\Component\VarExporter\LazyObjectInterface %A @@ -35,9 +35,11 @@ class ProjectServiceContainer extends Container protected $targetDir; protected $parameters = []; private $buildParameters; + protected readonly \WeakReference $ref; public function __construct(array $buildParameters = [], $containerDir = __DIR__) { + $this->ref = \WeakReference::create($this); $this->buildParameters = $buildParameters; $this->containerDir = $containerDir; $this->targetDir = \dirname($containerDir); @@ -50,7 +52,7 @@ class ProjectServiceContainer extends Container $this->aliases = []; - $this->privates['service_container'] = function () { + $this->privates['service_container'] = static function ($container) { include_once __DIR__.'/proxy-classes.php'; }; } @@ -75,13 +77,15 @@ class ProjectServiceContainer extends Container * * @return object A %lazy_foo_class% instance */ - protected function getLazyFooService($lazyLoad = true) + protected static function getLazyFooService($container, $lazyLoad = true) { + $containerRef = $container->ref; + if (true === $lazyLoad) { - return $this->services['lazy_foo'] = $this->createProxy('FooClassGhost2b16075', fn () => \FooClassGhost2b16075::createLazyGhost($this->getLazyFooService(...))); + return $container->services['lazy_foo'] = $container->createProxy('FooClassGhost2b16075', static fn () => \FooClassGhost2b16075::createLazyGhost(static fn ($proxy) => self::getLazyFooService($containerRef->get(), $proxy))); } - include_once $this->targetDir.''.'/Fixtures/includes/foo_lazy.php'; + include_once $container->targetDir.''.'/Fixtures/includes/foo_lazy.php'; return ($lazyLoad->__construct(new \Bar\FooLazyClass()) && false ?: $lazyLoad); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php index b07280c763cb3..ff6b744a8fa9c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'App\\Bus' => 'getBusService', @@ -53,13 +55,13 @@ public function getRemovedIds(): array * * @return \App\Bus */ - protected function getBusService() + protected static function getBusService($container) { - $a = ($this->services['App\\Db'] ?? $this->getDbService()); + $a = ($container->services['App\\Db'] ?? self::getDbService($container)); - $this->services['App\\Bus'] = $instance = new \App\Bus($a); + $container->services['App\\Bus'] = $instance = new \App\Bus($a); - $b = ($this->privates['App\\Schema'] ?? $this->getSchemaService()); + $b = ($container->privates['App\\Schema'] ?? self::getSchemaService($container)); $c = new \App\Registry(); $c->processor = [0 => $a, 1 => $instance]; @@ -76,11 +78,11 @@ protected function getBusService() * * @return \App\Db */ - protected function getDbService() + protected static function getDbService($container) { - $this->services['App\\Db'] = $instance = new \App\Db(); + $container->services['App\\Db'] = $instance = new \App\Db(); - $instance->schema = ($this->privates['App\\Schema'] ?? $this->getSchemaService()); + $instance->schema = ($container->privates['App\\Schema'] ?? self::getSchemaService($container)); return $instance; } @@ -90,14 +92,14 @@ protected function getDbService() * * @return \App\Schema */ - protected function getSchemaService() + protected static function getSchemaService($container) { - $a = ($this->services['App\\Db'] ?? $this->getDbService()); + $a = ($container->services['App\\Db'] ?? self::getDbService($container)); - if (isset($this->privates['App\\Schema'])) { - return $this->privates['App\\Schema']; + if (isset($container->privates['App\\Schema'])) { + return $container->privates['App\\Schema']; } - return $this->privates['App\\Schema'] = new \App\Schema($a); + return $container->privates['App\\Schema'] = new \App\Schema($a); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index 9762e7411df37..1f79a3e7dda55 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'bar2' => 'getBar2Service', @@ -104,11 +106,11 @@ public function getRemovedIds(): array * * @return \BarCircular */ - protected function getBar2Service() + protected static function getBar2Service($container) { - $this->services['bar2'] = $instance = new \BarCircular(); + $container->services['bar2'] = $instance = new \BarCircular(); - $instance->addFoobar(new \FoobarCircular(($this->services['foo2'] ?? $this->getFoo2Service()))); + $instance->addFoobar(new \FoobarCircular(($container->services['foo2'] ?? self::getFoo2Service($container)))); return $instance; } @@ -118,9 +120,9 @@ protected function getBar2Service() * * @return \BarCircular */ - protected function getBar3Service() + protected static function getBar3Service($container) { - $this->services['bar3'] = $instance = new \BarCircular(); + $container->services['bar3'] = $instance = new \BarCircular(); $a = new \FoobarCircular(); @@ -134,11 +136,11 @@ protected function getBar3Service() * * @return \stdClass */ - protected function getBaz6Service() + protected static function getBaz6Service($container) { - $this->services['baz6'] = $instance = new \stdClass(); + $container->services['baz6'] = $instance = new \stdClass(); - $instance->bar6 = ($this->privates['bar6'] ?? $this->getBar6Service()); + $instance->bar6 = ($container->privates['bar6'] ?? self::getBar6Service($container)); return $instance; } @@ -148,17 +150,17 @@ protected function getBaz6Service() * * @return \stdClass */ - protected function getConnectionService() + protected static function getConnectionService($container) { $a = new \stdClass(); $b = new \stdClass(); - $this->services['connection'] = $instance = new \stdClass($a, $b); + $container->services['connection'] = $instance = new \stdClass($a, $b); - $b->logger = ($this->services['logger'] ?? $this->getLoggerService()); + $b->logger = ($container->services['logger'] ?? self::getLoggerService($container)); - $a->subscriber = ($this->services['subscriber'] ?? $this->getSubscriberService()); + $a->subscriber = ($container->services['subscriber'] ?? self::getSubscriberService($container)); return $instance; } @@ -168,17 +170,17 @@ protected function getConnectionService() * * @return \stdClass */ - protected function getConnection2Service() + protected static function getConnection2Service($container) { $a = new \stdClass(); $b = new \stdClass(); - $this->services['connection2'] = $instance = new \stdClass($a, $b); + $container->services['connection2'] = $instance = new \stdClass($a, $b); $c = new \stdClass($instance); - $d = ($this->services['manager2'] ?? $this->getManager2Service()); + $d = ($container->services['manager2'] ?? self::getManager2Service($container)); $c->handler2 = new \stdClass($d); @@ -194,15 +196,19 @@ protected function getConnection2Service() * * @return \stdClass */ - protected function getDoctrine_EntityManagerService() + protected static function getDoctrine_EntityManagerService($container) { + $containerRef = $container->ref; + $a = new \stdClass(); - $a->resolver = new \stdClass(new RewindableGenerator(function () { - yield 0 => ($this->privates['doctrine.listener'] ?? $this->getDoctrine_ListenerService()); + $a->resolver = new \stdClass(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 0 => ($container->privates['doctrine.listener'] ?? self::getDoctrine_ListenerService($container)); }, 1)); $a->flag = 'ok'; - return $this->services['doctrine.entity_manager'] = \FactoryChecker::create($a); + return $container->services['doctrine.entity_manager'] = \FactoryChecker::create($a); } /** @@ -210,11 +216,11 @@ protected function getDoctrine_EntityManagerService() * * @return \FooCircular */ - protected function getFooService() + protected static function getFooService($container) { $a = new \BarCircular(); - $this->services['foo'] = $instance = new \FooCircular($a); + $container->services['foo'] = $instance = new \FooCircular($a); $a->addFoobar(new \FoobarCircular($instance)); @@ -226,15 +232,15 @@ protected function getFooService() * * @return \FooCircular */ - protected function getFoo2Service() + protected static function getFoo2Service($container) { - $a = ($this->services['bar2'] ?? $this->getBar2Service()); + $a = ($container->services['bar2'] ?? self::getBar2Service($container)); - if (isset($this->services['foo2'])) { - return $this->services['foo2']; + if (isset($container->services['foo2'])) { + return $container->services['foo2']; } - return $this->services['foo2'] = new \FooCircular($a); + return $container->services['foo2'] = new \FooCircular($a); } /** @@ -242,9 +248,9 @@ protected function getFoo2Service() * * @return \stdClass */ - protected function getFoo5Service() + protected static function getFoo5Service($container) { - $this->services['foo5'] = $instance = new \stdClass(); + $container->services['foo5'] = $instance = new \stdClass(); $a = new \stdClass($instance); $a->foo = $instance; @@ -259,11 +265,11 @@ protected function getFoo5Service() * * @return \stdClass */ - protected function getFoo6Service() + protected static function getFoo6Service($container) { - $this->services['foo6'] = $instance = new \stdClass(); + $container->services['foo6'] = $instance = new \stdClass(); - $instance->bar6 = ($this->privates['bar6'] ?? $this->getBar6Service()); + $instance->bar6 = ($container->privates['bar6'] ?? self::getBar6Service($container)); return $instance; } @@ -273,11 +279,11 @@ protected function getFoo6Service() * * @return \stdClass */ - protected function getFoobar4Service() + protected static function getFoobar4Service($container) { $a = new \stdClass(); - $this->services['foobar4'] = $instance = new \stdClass($a); + $container->services['foobar4'] = $instance = new \stdClass($a); $a->foobar = $instance; @@ -289,11 +295,11 @@ protected function getFoobar4Service() * * @return \stdClass */ - protected function getListener3Service() + protected static function getListener3Service($container) { - $this->services['listener3'] = $instance = new \stdClass(); + $container->services['listener3'] = $instance = new \stdClass(); - $instance->manager = ($this->services['manager3'] ?? $this->getManager3Service()); + $instance->manager = ($container->services['manager3'] ?? self::getManager3Service($container)); return $instance; } @@ -303,15 +309,15 @@ protected function getListener3Service() * * @return \stdClass */ - protected function getListener4Service() + protected static function getListener4Service($container) { - $a = ($this->privates['manager4'] ?? $this->getManager4Service()); + $a = ($container->privates['manager4'] ?? self::getManager4Service($container)); - if (isset($this->services['listener4'])) { - return $this->services['listener4']; + if (isset($container->services['listener4'])) { + return $container->services['listener4']; } - return $this->services['listener4'] = new \stdClass($a); + return $container->services['listener4'] = new \stdClass($a); } /** @@ -319,17 +325,17 @@ protected function getListener4Service() * * @return \stdClass */ - protected function getLoggerService() + protected static function getLoggerService($container) { - $a = ($this->services['connection'] ?? $this->getConnectionService()); + $a = ($container->services['connection'] ?? self::getConnectionService($container)); - if (isset($this->services['logger'])) { - return $this->services['logger']; + if (isset($container->services['logger'])) { + return $container->services['logger']; } - $this->services['logger'] = $instance = new \stdClass($a); + $container->services['logger'] = $instance = new \stdClass($a); - $instance->handler = new \stdClass(($this->services['manager'] ?? $this->getManagerService())); + $instance->handler = new \stdClass(($container->services['manager'] ?? self::getManagerService($container))); return $instance; } @@ -339,15 +345,15 @@ protected function getLoggerService() * * @return \stdClass */ - protected function getManagerService() + protected static function getManagerService($container) { - $a = ($this->services['connection'] ?? $this->getConnectionService()); + $a = ($container->services['connection'] ?? self::getConnectionService($container)); - if (isset($this->services['manager'])) { - return $this->services['manager']; + if (isset($container->services['manager'])) { + return $container->services['manager']; } - return $this->services['manager'] = new \stdClass($a); + return $container->services['manager'] = new \stdClass($a); } /** @@ -355,15 +361,15 @@ protected function getManagerService() * * @return \stdClass */ - protected function getManager2Service() + protected static function getManager2Service($container) { - $a = ($this->services['connection2'] ?? $this->getConnection2Service()); + $a = ($container->services['connection2'] ?? self::getConnection2Service($container)); - if (isset($this->services['manager2'])) { - return $this->services['manager2']; + if (isset($container->services['manager2'])) { + return $container->services['manager2']; } - return $this->services['manager2'] = new \stdClass($a); + return $container->services['manager2'] = new \stdClass($a); } /** @@ -371,17 +377,17 @@ protected function getManager2Service() * * @return \stdClass */ - protected function getManager3Service($lazyLoad = true) + protected static function getManager3Service($container, $lazyLoad = true) { - $a = ($this->services['listener3'] ?? $this->getListener3Service()); + $a = ($container->services['listener3'] ?? self::getListener3Service($container)); - if (isset($this->services['manager3'])) { - return $this->services['manager3']; + if (isset($container->services['manager3'])) { + return $container->services['manager3']; } $b = new \stdClass(); $b->listener = [0 => $a]; - return $this->services['manager3'] = new \stdClass($b); + return $container->services['manager3'] = new \stdClass($b); } /** @@ -389,11 +395,11 @@ protected function getManager3Service($lazyLoad = true) * * @return \stdClass */ - protected function getMonolog_LoggerService() + protected static function getMonolog_LoggerService($container) { - $this->services['monolog.logger'] = $instance = new \stdClass(); + $container->services['monolog.logger'] = $instance = new \stdClass(); - $instance->handler = ($this->privates['mailer.transport'] ?? $this->getMailer_TransportService()); + $instance->handler = ($container->privates['mailer.transport'] ?? self::getMailer_TransportService($container)); return $instance; } @@ -403,11 +409,11 @@ protected function getMonolog_LoggerService() * * @return \stdClass */ - protected function getMonologInline_LoggerService() + protected static function getMonologInline_LoggerService($container) { - $this->services['monolog_inline.logger'] = $instance = new \stdClass(); + $container->services['monolog_inline.logger'] = $instance = new \stdClass(); - $instance->handler = ($this->privates['mailer_inline.mailer'] ?? $this->getMailerInline_MailerService()); + $instance->handler = ($container->privates['mailer_inline.mailer'] ?? self::getMailerInline_MailerService($container)); return $instance; } @@ -417,19 +423,19 @@ protected function getMonologInline_LoggerService() * * @return \stdClass */ - protected function getPAService() + protected static function getPAService($container) { $a = new \stdClass(); - $b = ($this->privates['pC'] ?? $this->getPCService()); + $b = ($container->privates['pC'] ?? self::getPCService($container)); - if (isset($this->services['pA'])) { - return $this->services['pA']; + if (isset($container->services['pA'])) { + return $container->services['pA']; } - $this->services['pA'] = $instance = new \stdClass($a, $b); + $container->services['pA'] = $instance = new \stdClass($a, $b); - $a->d = ($this->privates['pD'] ?? $this->getPDService()); + $a->d = ($container->privates['pD'] ?? self::getPDService($container)); return $instance; } @@ -439,15 +445,15 @@ protected function getPAService() * * @return \stdClass */ - protected function getRootService() + protected static function getRootService($container) { $a = new \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls(); $b = new \stdClass(); - $a->call(new \stdClass(new \stdClass($b, ($this->privates['level5'] ?? $this->getLevel5Service())))); + $a->call(new \stdClass(new \stdClass($b, ($container->privates['level5'] ?? self::getLevel5Service($container))))); - return $this->services['root'] = new \stdClass($a, $b); + return $container->services['root'] = new \stdClass($a, $b); } /** @@ -455,15 +461,15 @@ protected function getRootService() * * @return \stdClass */ - protected function getSubscriberService() + protected static function getSubscriberService($container) { - $a = ($this->services['manager'] ?? $this->getManagerService()); + $a = ($container->services['manager'] ?? self::getManagerService($container)); - if (isset($this->services['subscriber'])) { - return $this->services['subscriber']; + if (isset($container->services['subscriber'])) { + return $container->services['subscriber']; } - return $this->services['subscriber'] = new \stdClass($a); + return $container->services['subscriber'] = new \stdClass($a); } /** @@ -471,15 +477,15 @@ protected function getSubscriberService() * * @return \stdClass */ - protected function getBar6Service() + protected static function getBar6Service($container) { - $a = ($this->services['foo6'] ?? $this->getFoo6Service()); + $a = ($container->services['foo6'] ?? self::getFoo6Service($container)); - if (isset($this->privates['bar6'])) { - return $this->privates['bar6']; + if (isset($container->privates['bar6'])) { + return $container->privates['bar6']; } - return $this->privates['bar6'] = new \stdClass($a); + return $container->privates['bar6'] = new \stdClass($a); } /** @@ -487,9 +493,9 @@ protected function getBar6Service() * * @return \stdClass */ - protected function getDoctrine_ListenerService() + protected static function getDoctrine_ListenerService($container) { - return $this->privates['doctrine.listener'] = new \stdClass(($this->services['doctrine.entity_manager'] ?? $this->getDoctrine_EntityManagerService())); + return $container->privates['doctrine.listener'] = new \stdClass(($container->services['doctrine.entity_manager'] ?? self::getDoctrine_EntityManagerService($container))); } /** @@ -497,11 +503,11 @@ protected function getDoctrine_ListenerService() * * @return \stdClass */ - protected function getLevel5Service() + protected static function getLevel5Service($container) { $a = new \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls(); - $this->privates['level5'] = $instance = new \stdClass($a); + $container->privates['level5'] = $instance = new \stdClass($a); $a->call($instance); @@ -513,11 +519,15 @@ protected function getLevel5Service() * * @return \stdClass */ - protected function getMailer_TransportService() + protected static function getMailer_TransportService($container) { - return $this->privates['mailer.transport'] = (new \FactoryCircular(new RewindableGenerator(function () { - yield 0 => ($this->privates['mailer.transport_factory.amazon'] ?? $this->getMailer_TransportFactory_AmazonService()); - yield 1 => $this->getMailerInline_TransportFactory_AmazonService(); + $containerRef = $container->ref; + + return $container->privates['mailer.transport'] = (new \FactoryCircular(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 0 => ($container->privates['mailer.transport_factory.amazon'] ?? self::getMailer_TransportFactory_AmazonService($container)); + yield 1 => self::getMailerInline_TransportFactory_AmazonService($container); }, 2)))->create(); } @@ -526,13 +536,13 @@ protected function getMailer_TransportService() * * @return \stdClass */ - protected function getMailer_TransportFactory_AmazonService() + protected static function getMailer_TransportFactory_AmazonService($container) { $a = new \stdClass(); - $this->privates['mailer.transport_factory.amazon'] = $instance = new \stdClass($a); + $container->privates['mailer.transport_factory.amazon'] = $instance = new \stdClass($a); - $a->handler = ($this->privates['mailer.transport'] ?? $this->getMailer_TransportService()); + $a->handler = ($container->privates['mailer.transport'] ?? self::getMailer_TransportService($container)); return $instance; } @@ -542,9 +552,9 @@ protected function getMailer_TransportFactory_AmazonService() * 10000 * @return \stdClass */ - protected function getMailerInline_MailerService() + protected static function getMailerInline_MailerService($container) { - return $this->privates['mailer_inline.mailer'] = new \stdClass((new \FactoryCircular(new RewindableGenerator(function () { + return $container->privates['mailer_inline.mailer'] = new \stdClass((new \FactoryCircular(new RewindableGenerator(static function () { return new \EmptyIterator(); }, 0)))->create()); } @@ -554,10 +564,10 @@ protected function getMailerInline_MailerService() * * @return \stdClass */ - protected function getMailerInline_TransportFactory_AmazonService() + protected static function getMailerInline_TransportFactory_AmazonService($container) { $a = new \stdClass(); - $a->handler = ($this->privates['mailer_inline.mailer'] ?? $this->getMailerInline_MailerService()); + $a->handler = ($container->privates['mailer_inline.mailer'] ?? self::getMailerInline_MailerService($container)); return new \stdClass($a); } @@ -567,13 +577,13 @@ protected function getMailerInline_TransportFactory_AmazonService() * * @return \stdClass */ - protected function getManager4Service($lazyLoad = true) + protected static function getManager4Service($container, $lazyLoad = true) { $a = new \stdClass(); - $this->privates['manager4'] = $instance = new \stdClass($a); + $container->privates['manager4'] = $instance = new \stdClass($a); - $a->listener = [0 => ($this->services['listener4'] ?? $this->getListener4Service())]; + $a->listener = [0 => ($container->services['listener4'] ?? self::getListener4Service($container))]; return $instance; } @@ -583,11 +593,11 @@ protected function getManager4Service($lazyLoad = true) * * @return \stdClass */ - protected function getPCService($lazyLoad = true) + protected static function getPCService($container, $lazyLoad = true) { - $this->privates['pC'] = $instance = new \stdClass(); + $container->privates['pC'] = $instance = new \stdClass(); - $instance->d = ($this->privates['pD'] ?? $this->getPDService()); + $instance->d = ($container->privates['pD'] ?? self::getPDService($container)); return $instance; } @@ -597,14 +607,14 @@ protected function getPCService($lazyLoad = true) * * @return \stdClass */ - protected function getPDService() + protected static function getPDService($container) { - $a = ($this->services['pA'] ?? $this->getPAService()); + $a = ($container->services['pA'] ?? self::getPAService($container)); - if (isset($this->privates['pD'])) { - return $this->privates['pD']; + if (isset($container->privates['pD'])) { + return $container->privates['pD']; } - return $this->privates['pD'] = new \stdClass($a); + return $container->privates['pD'] = new \stdClass($a); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index bff47395118b4..f7dc4133f3d4a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'bar' => 'getBarService', @@ -104,11 +106,11 @@ public function getRemovedIds(): array * * @return \BarCircular */ - protected function getBarService() + protected static function getBarService($container) { - $this->services['bar'] = $instance = new \BarCircular(); + $container->services['bar'] = $instance = new \BarCircular(); - $instance->addFoobar(($this->services['foobar'] ?? $this->getFoobarService())); + $instance->addFoobar(($container->services['foobar'] ?? self::getFoobarService($container))); return $instance; } @@ -118,11 +120,11 @@ protected function getBarService() * * @return \BarCircular */ - protected function getBar3Service() + protected static function getBar3Service($container) { - $this->services['bar3'] = $instance = new \BarCircular(); + $container->services['bar3'] = $instance = new \BarCircular(); - $a = ($this->services['foobar3'] ??= new \FoobarCircular()); + $a = ($container->services['foobar3'] ??= new \FoobarCircular()); $instance->addFoobar($a, $a); @@ -134,15 +136,15 @@ protected function getBar3Service() * * @return \stdClass */ - protected function getBar5Service() + protected static function getBar5Service($container) { - $a = ($this->services['foo5'] ?? $this->getFoo5Service()); + $a = ($container->services['foo5'] ?? self::getFoo5Service($container)); - if (isset($this->services['bar5'])) { - return $this->services['bar5']; + if (isset($container->services['bar5'])) { + return $container->services['bar5']; } - $this->services['bar5'] = $instance = new \stdClass($a); + $container->services['bar5'] = $instance = new \stdClass($a); $instance->foo = $a; @@ -154,11 +156,11 @@ protected function getBar5Service() * * @return \stdClass */ - protected function getBaz6Service() + protected static function getBaz6Service($container) { - $this->services['baz6'] = $instance = new \stdClass(); + $container->services['baz6'] = $instance = new \stdClass(); - $instance->bar6 = ($this->privates['bar6'] ?? $this->getBar6Service()); + $instance->bar6 = ($container->privates['bar6'] ?? self::getBar6Service($container)); return $instance; } @@ -168,18 +170,18 @@ protected function getBaz6Service() * * @return \stdClass */ - protected function getConnectionService() + protected static function getConnectionService($container) { - $a = ($this->services['dispatcher'] ?? $this->getDispatcherService()); + $a = ($container->services['dispatcher'] ?? self::getDispatcherService($container)); - if (isset($this->services['connection'])) { - return $this->services['connection']; + if (isset($container->services['connection'])) { + return $container->services['connection']; } $b = new \stdClass(); - $this->services['connection'] = $instance = new \stdClass($a, $b); + $container->services['connection'] = $instance = new \stdClass($a, $b); - $b->logger = ($this->services['logger'] ?? $this->getLoggerService()); + $b->logger = ($container->services['logger'] ?? self::getLoggerService($container)); return $instance; } @@ -189,19 +191,19 @@ protected function getConnectionService() * * @return \stdClass */ - protected function getConnection2Service() + protected static function getConnection2Service($container) { - $a = ($this->services['dispatcher2'] ?? $this->getDispatcher2Service()); + $a = ($container->services['dispatcher2'] ?? self::getDispatcher2Service($container)); - if (isset($this->services['connection2'])) { - return $this->services['connection2']; + if (isset($container->services['connection2'])) { + return $container->services['connection2']; } $b = new \stdClass(); - $this->services['connection2'] = $instance = new \stdClass($a, $b); + $container->services['connection2'] = $instance = new \stdClass($a, $b); $c = new \stdClass($instance); - $c->handler2 = new \stdClass(($this->services['manager2'] ?? $this->getManager2Service())); + $c->handler2 = new \stdClass(($container->services['manager2'] ?? self::getManager2Service($container))); $b->logger2 = $c; @@ -213,11 +215,11 @@ protected function getConnection2Service() * * @return \stdClass */ - protected function getConnection3Service() + protected static function getConnection3Service($container) { - $this->services['connection3'] = $instance = new \stdClass(); + $container->services['connection3'] = $instance = new \stdClass(); - $instance->listener = [0 => ($this->services['listener3'] ?? $this->getListener3Service())]; + $instance->listener = [0 => ($container->services['listener3'] ?? self::getListener3Service($container))]; return $instance; } @@ -227,11 +229,11 @@ protected function getConnection3Service() * * @return \stdClass */ - protected function getConnection4Service() + protected static function getConnection4Service($container) { - $this->services['connection4'] = $instance = new \stdClass(); + $container->services['connection4'] = $instance = new \stdClass(); - $instance->listener = [0 => ($this->services['listener4'] ?? $this->getListener4Service())]; + $instance->listener = [0 => ($container->services['listener4'] ?? self::getListener4Service($container))]; return $instance; } @@ -241,11 +243,11 @@ protected function getConnection4Service() * * @return \stdClass */ - protected function getDispatcherService($lazyLoad = true) + protected static function getDispatcherService($container, $lazyLoad = true) { - $this->services['dispatcher'] = $instance = new \stdClass(); + $container->services['dispatcher'] = $instance = new \stdClass(); - $instance->subscriber = ($this->services['subscriber'] ?? $this->getSubscriberService()); + $instance->subscriber = ($container->services['subscriber'] ?? self::getSubscriberService($container)); return $instance; } @@ -255,11 +257,11 @@ protected function getDispatcherService($lazyLoad = true) * * @return \stdClass */ - protected function getDispatcher2Service($lazyLoad = true) + protected static function getDispatcher2Service($container, $lazyLoad = true) { - $this->services['dispatcher2'] = $instance = new \stdClass(); + $container->services['dispatcher2'] = $instance = new \stdClass(); - $instance->subscriber2 = new \stdClass(($this->services['manager2'] ?? $this->getManager2Service())); + $instance->subscriber2 = new \stdClass(($container->services['manager2'] ?? self::getManager2Service($container))); return $instance; } @@ -269,10 +271,14 @@ protected function getDispatcher2Service($lazyLoad = true) * * @return \stdClass */ - protected function getDoctrine_EntityListenerResolverService() + protected static function getDoctrine_EntityListenerResolverService($container) { - return $this->services['doctrine.entity_listener_resolver'] = new \stdClass(new RewindableGenerator(function () { - yield 0 => ($this->services['doctrine.listener'] ?? $this->getDoctrine_ListenerService()); + $containerRef = $container->ref; + + return $container->services['doctrine.entity_listener_resolver'] = new \stdClass(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 0 => ($container->services['doctrine.listener'] ?? self::getDoctrine_ListenerService($container)); }, 1)); } @@ -281,13 +287,13 @@ protected function getDoctrine_EntityListenerResolverService() * * @return \stdClass */ - protected function getDoctrine_EntityManagerService() + protected static function getDoctrine_EntityManagerService($container) { $a = new \stdClass(); - $a->resolver = ($this->services['doctrine.entity_listener_resolver'] ?? $this->getDoctrine_EntityListenerResolverService()); + $a->resolver = ($container->services['doctrine.entity_listener_resolver'] ?? self::getDoctrine_EntityListenerResolverService($container)); $a->flag = 'ok'; - return $this->services['doctrine.entity_manager'] = \FactoryChecker::create($a); + return $container->services['doctrine.entity_manager'] = \FactoryChecker::create($a); } /** @@ -295,9 +301,9 @@ protected function getDoctrine_EntityManagerService() * * @return \stdClass */ - protected function getDoctrine_ListenerService() + protected static function getDoctrine_ListenerService($container) { - return $this->services['doctrine.listener'] = new \stdClass(($this->services['doctrine.entity_manager'] ?? $this->getDoctrine_EntityManagerService())); + return $container->services['doctrine.listener'] = new \stdClass(($container->services['doctrine.entity_manager'] ?? self::getDoctrine_EntityManagerService($container))); } /** @@ -305,15 +311,15 @@ protected function getDoctrine_ListenerService() * * @return \FooCircular */ - protected function getFooService() + protected static function getFooService($container) { - $a = ($this->services['bar'] ?? $this->getBarService()); + $a = ($container->services['bar'] ?? self::getBarService($container)); - if (isset($this->services['foo'])) { - return $this->services['foo']; + if (isset($container->services['foo'])) { + return $container->services['foo']; } - return $this->services['foo'] = new \FooCircular($a); + return $container->services['foo'] = new \FooCircular($a); } /** @@ -321,13 +327,13 @@ protected function getFooService() * * @return \FooCircular */ - protected function getFoo2Service() + protected static function getFoo2Service($container) { $a = new \BarCircular(); - $this->services['foo2'] = $instance = new \FooCircular($a); + $container->services['foo2'] = $instance = new \FooCircular($a); - $a->addFoobar(($this->services['foobar2'] ?? $this->getFoobar2Service())); + $a->addFoobar(($container->services['foobar2'] ?? self::getFoobar2Service($container))); return $instance; } @@ -337,17 +343,17 @@ protected function getFoo2Service() * * @return \stdClass */ - protected function getFoo4Service() + protected static function getFoo4Service($container) { - $this->factories['foo4'] = function () { + $container->factories['foo4'] = static function ($container) { $instance = new \stdClass(); - $instance->foobar = ($this->services['foobar4'] ?? $this->getFoobar4Service()); + $instance->foobar = ($container->services['foobar4'] ?? self::getFoobar4Service($container)); return $instance; }; - return $this->factories['foo4'](); + return $container->factories['foo4']($container); } /** @@ -355,11 +361,11 @@ protected function getFoo4Service() * * @return \stdClass */ - protected function getFoo5Service() + protected static function getFoo5Service($container) { - $this->services['foo5'] = $instance = new \stdClass(); + $container->services['foo5'] = $instance = new \stdClass(); - $instance->bar = ($this->services['bar5'] ?? $this->getBar5Service()); + $instance->bar = ($container->services['bar5'] ?? self::getBar5Service($container)); return $instance; } @@ -369,11 +375,11 @@ protected function getFoo5Service() * * @return \stdClass */ - protected function getFoo6Service() + protected static function getFoo6Service($container) { - $this->services['foo6'] = $instance = new \stdClass(); + $container->services['foo6'] = $instance = new \stdClass(); - $instance->bar6 = ($this->privates['bar6'] ?? $this->getBar6Service()); + $instance->bar6 = ($container->privates['bar6'] ?? self::getBar6Service($container)); return $instance; } @@ -383,15 +389,15 @@ protected function getFoo6Service() * * @return \FoobarCircular */ - protected function getFoobarService() + protected static function getFoobarService($container) { - $a = ($this->services['foo'] ?? $this->getFooService()); + $a = ($container->services['foo'] ?? self::getFooService($container)); - if (isset($this->services['foobar'])) { - return $this->services['foobar']; + if (isset($container->services['foobar'])) { + return $container->services['foobar']; } - return $this->services['foobar'] = new \FoobarCircular($a); + return $container->services['foobar'] = new \FoobarCircular($a); } /** @@ -399,15 +405,15 @@ protected function getFoobarService() * * @return \FoobarCircular */ - protected function getFoobar2Service() + protected static function getFoobar2Service($container) { - $a = ($this->services['foo2'] ?? $this->getFoo2Service()); + $a = ($container->services['foo2'] ?? self::getFoo2Service($container)); - if (isset($this->services['foobar2'])) { - return $this->services['foobar2']; + if (isset($container->services['foobar2'])) { + return $container->services['foobar2']; } - return $this->services['foobar2'] = new \FoobarCircular($a); + return $container->services['foobar2'] = new \FoobarCircular($a); } /** @@ -415,9 +421,9 @@ protected function getFoobar2Service() * * @return \FoobarCircular */ - protected function getFoobar3Service() + protected static function getFoobar3Service($container) { - return $this->services['foobar3'] = new \FoobarCircular(); + return $container->services['foobar3'] = new \FoobarCircular(); } /** @@ -425,11 +431,11 @@ protected function getFoobar3Service() * * @return \stdClass */ - protected function getFoobar4Service() + protected static function getFoobar4Service($container) { $a = new \stdClass(); - $this->services['foobar4'] = $instance = new \stdClass($a); + $container->services['foobar4'] = $instance = new \stdClass($a); $a->foobar = $instance; @@ -441,11 +447,11 @@ protected function getFoobar4Service() * * @return \stdClass */ - protected function getListener3Service() + protected static function getListener3Service($container) { - $this->services['listener3'] = $instance = new \stdClass(); + $container->services['listener3'] = $instance = new \stdClass(); - $instance->manager = ($this->services['manager3'] ?? $this->getManager3Service()); + $instance->manager = ($container->services['manager3'] ?? self::getManager3Service($container)); return $instance; } @@ -455,15 +461,15 @@ protected function getListener3Service() * * @return \stdClass */ - protected function getListener4Service() + protected static function getListener4Service($container) { - $a = ($this->privates['manager4'] ?? $this->getManager4Service()); + $a = ($container->privates['manager4'] ?? self::getManager4Service($container)); - if (isset($this->services['listener4'])) { - return $this->services['listener4']; + if (isset($container->services['listener4'])) { + return $container->services['listener4']; } - return $this->services['listener4'] = new \stdClass($a); + return $container->services['listener4'] = new \stdClass($a); } /** @@ -471,17 +477,17 @@ protected function getListener4Service() * * @return \stdClass */ - protected function getLoggerService() + protected static function getLoggerService($container) { - $a = ($this->services['connection'] ?? $this->getConnectionService()); + $a = ($container->services['connection'] ?? self::getConnectionService($container)); - if (isset($this->services['logger'])) { - return $this->services['logger']; + if (isset($container->services['logger'])) { + return $container->services['logger']; } - $this->services['logger'] = $instance = new \stdClass($a); + $container->services['logger'] = $instance = new \stdClass($a); - $instance->handler = new \stdClass(($this->services['manager'] ?? $this->getManagerService())); + $instance->handler = new \stdClass(($container->services['manager'] ?? self::getManagerService($container))); return $instance; } @@ -491,9 +497,9 @@ protected function getLoggerService() * * @return \stdClass */ - protected function getMailer_TransportService() + protected static function getMailer_TransportService($container) { - return $this->services['mailer.transport'] = ($this->services['mailer.transport_factory'] ?? $this->getMailer_TransportFactoryService())->create(); + return $container->services['mailer.transport'] = ($container->services['mailer.transport_factory'] ?? self::getMailer_TransportFactoryService($container))->create(); } /** @@ -501,11 +507,15 @@ protected function getMailer_TransportService() * * @return \FactoryCircular */ - protected function getMailer_TransportFactoryService() + protected static function getMailer_TransportFactoryService($container) { - return $this->services['mailer.transport_factory'] = new \FactoryCircular(new RewindableGenerator(function () { - yield 0 => ($this->services['mailer.transport_factory.amazon'] ?? $this->getMailer_TransportFactory_AmazonService()); - yield 1 => ($this->services['mailer_inline.transport_factory.amazon'] ?? $this->getMailerInline_TransportFactory_AmazonService()); + $containerRef = $container->ref; + + return $container->services['mailer.transport_factory'] = new \FactoryCircular(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 0 => ($container->services['mailer.transport_factory.amazon'] ?? self::getMailer_TransportFactory_AmazonService($container)); + yield 1 => ($container->services['mailer_inline.transport_factory.amazon'] ?? self::getMailerInline_TransportFactory_AmazonService($container)); }, 2)); } @@ -514,9 +524,9 @@ protected function getMailer_TransportFactoryService() * * @return \stdClass */ - protected function getMailer_TransportFactory_AmazonService() + protected static function getMailer_TransportFactory_AmazonService($container) { - return $this->services['mailer.transport_factory.amazon'] = new \stdClass(($this->services['monolog.logger_2'] ?? $this->getMonolog_Logger2Service())); + return $container->services['mailer.transport_factory.amazon'] = new \stdClass(($container->services['monolog.logger_2'] ?? self::getMonolog_Logger2Service($container))); } /** @@ -524,9 +534,9 @@ protected function getMailer_TransportFactory_AmazonService() * * @return \FactoryCircular */ - protected function getMailerInline_TransportFactoryService() + protected static function getMailerInline_TransportFactoryService($container) { - return $this->services['mailer_inline.transport_factory'] = new \FactoryCircular(new RewindableGenerator(function () { + return $container->services['mailer_inline.transport_factory'] = new \FactoryCircular(new RewindableGenerator(static function () { return new \EmptyIterator(); }, 0)); } @@ -536,9 +546,9 @@ protected function getMailerInline_TransportFactoryService() * * @return \stdClass */ - protected function getMailerInline_TransportFactory_AmazonService() + protected static function getMailerInline_TransportFactory_AmazonService($container) { - return $this->services['mailer_inline.transport_factory.amazon'] = new \stdClass(($this->services['monolog_inline.logger_2'] ?? $this->getMonologInline_Logger2Service())); + return $container->services['mailer_inline.transport_factory.amazon'] = new \stdClass(($container->services['monolog_inline.logger_2'] ?? self::getMonologInline_Logger2Service($container))); } /** @@ -546,15 +556,15 @@ protected function getMailerInline_TransportFactory_AmazonService() * * @return \stdClass */ - protected function getManagerService() + protected static function getManagerService($container) { - $a = ($this->services['connection'] ?? $this->getConnectionService()); + $a = ($container->services['connection'] ?? self::getConnectionService($container)); - if (isset($this->services['manager'])) { - return $this->services['manager']; + if (isset($container->services['manager'])) { + return $container->services['manager']; } - return $this->services['manager'] = new \stdClass($a); + return $container->services['manager'] = new \stdClass($a); } /** @@ -562,15 +572,15 @@ protected function getManagerService() * * @return \stdClass */ - protected function getManager2Service() + protected static function getManager2Service($container) { - $a = ($this->services['connection2'] ?? $this->getConnection2Service()); + $a = ($container->services['connection2'] ?? self::getConnection2Service($container)); - if (isset($this->services['manager2'])) { - return $this->services['manager2']; + if (isset($container->services['manager2'])) { + return $container->services['manager2']; } - return $this->services['manager2'] = new \stdClass($a); + return $container->services['manager2'] = new \stdClass($a); } /** @@ -578,15 +588,15 @@ protected function getManager2Service() * * @return \stdClass */ - protected function getManager3Service($lazyLoad = true) + protected static function getManager3Service($container, $lazyLoad = true) { - $a = ($this->services['connection3'] ?? $this->getConnection3Service()); + $a = ($container->services['connection3'] ?? self::getConnection3Service($container)); - if (isset($this->services['manager3'])) { - return $this->services['manager3']; + if (isset($container->services['manager3'])) { + return $container->services['manager3']; } - return $this->services['manager3'] = new \stdClass($a); + return $container->services['manager3'] = new \stdClass($a); } /** @@ -594,11 +604,11 @@ protected function getManager3Service($lazyLoad = true) * * @return \stdClass */ - protected function getMonolog_LoggerService() + protected static function getMonolog_LoggerService($container) { - $this->services['monolog.logger'] = $instance = new \stdClass(); + $container->services['monolog.logger'] = $instance = new \stdClass(); - $instance->handler = ($this->services['mailer.transport'] ?? $this->getMailer_TransportService()); + $instance->handler = ($container->services['mailer.transport'] ?? self::getMailer_TransportService($container)); return $instance; } @@ -608,11 +618,11 @@ protected function getMonolog_LoggerService() * * @return \stdClass */ - protected function getMonolog_Logger2Service() + protected static function getMonolog_Logger2Service($container) { - $this->services['monolog.logger_2'] = $instance = new \stdClass(); + $container->services['monolog.logger_2'] = $instance = new \stdClass(); - $instance->handler = ($this->services['mailer.transport'] ?? $this->getMailer_TransportService()); + $instance->handler = ($container->services['mailer.transport'] ?? self::getMailer_TransportService($container)); return $instance; } @@ -622,11 +632,11 @@ protected function getMonolog_Logger2Service() * * @return \stdClass */ - protected function getMonologInline_LoggerService() + protected static function getMonologInline_LoggerService($container) { - $this->services['monolog_inline.logger'] = $instance = new \stdClass(); + $container->services['monolog_inline.logger'] = $instance = new \stdClass(); - $instance->handler = ($this->privates['mailer_inline.mailer'] ?? $this->getMailerInline_MailerService()); + $instance->handler = ($container->privates['mailer_inline.mailer'] ?? self::getMailerInline_MailerService($container)); return $instance; } @@ -636,11 +646,11 @@ protected function getMonologInline_LoggerService() * * @return \stdClass */ - protected function getMonologInline_Logger2Service() + protected static function getMonologInline_Logger2Service($container) { - $this->services['monolog_inline.logger_2'] = $instance = new \stdClass(); + $container->services['monolog_inline.logger_2'] = $instance = new \stdClass(); - $instance->handler = ($this->privates['mailer_inline.mailer'] ?? $this->getMailerInline_MailerService()); + $instance->handler = ($container->privates['mailer_inline.mailer'] ?? self::getMailerInline_MailerService($container)); return $instance; } @@ -650,20 +660,20 @@ protected function getMonologInline_Logger2Service() * * @return \stdClass */ - protected function getPAService() + protected static function getPAService($container) { - $a = ($this->services['pB'] ?? $this->getPBService()); + $a = ($container->services['pB'] ?? self::getPBService($container)); - if (isset($this->services['pA'])) { - return $this->services['pA']; + if (isset($container->services['pA'])) { + return $container->services['pA']; } - $b = ($this->services['pC'] ?? $this->getPCService()); + $b = ($container->services['pC'] ?? self::getPCService($container)); - if (isset($this->services['pA'])) { - return $this->services['pA']; + if (isset($container->services['pA'])) { + return $container->services['pA']; } - return $this->services['pA'] = new \stdClass($a, $b); + return $container->services['pA'] = new \stdClass($a, $b); } /** @@ -671,11 +681,11 @@ protected function getPAService() * * @return \stdClass */ - protected function getPBService() + protected static function getPBService($container) { - $this->services['pB'] = $instance = new \stdClass(); + $container->services['pB'] = $instance = new \stdClass(); - $instance->d = ($this->services['pD'] ?? $this->getPDService()); + $instance->d = ($container->services['pD'] ?? self::getPDService($container)); return $instance; } @@ -685,11 +695,11 @@ protected function getPBService() * * @return \stdClass */ - protected function getPCService($lazyLoad = true) + protected static function getPCService($container, $lazyLoad = true) { - $this->services['pC'] = $instance = new \stdClass(); + $container->services['pC'] = $instance = new \stdClass(); - $instance->d = ($this->services['pD'] ?? $this->getPDService()); + $instance->d = ($container->services['pD'] ?? self::getPDService($container)); return $instance; } @@ -699,15 +709,15 @@ protected function getPCService($lazyLoad = true) * * @return \stdClass */ - protected function getPDService() + protected static function getPDService($container) { - $a = ($this->services['pA'] ?? $this->getPAService()); + $a = ($container->services['pA'] ?? self::getPAService($container)); - if (isset($this->services['pD'])) { - return $this->services['pD']; + if (isset($container->services['pD'])) { + return $container->services['pD']; } - return $this->services['pD'] = new \stdClass($a); + return $container->services['pD'] = new \stdClass($a); } /** @@ -715,15 +725,15 @@ protected function getPDService() * * @return \stdClass */ - protected function getRootService() + protected static function getRootService($container) { $a = new \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls(); $b = new \stdClass(); - $a->call(new \stdClass(new \stdClass($b, ($this->privates['level5'] ?? $this->getLevel5Service())))); + $a->call(new \stdClass(new \stdClass($b, ($container->privates['level5'] ?? self::getLevel5Service($container))))); - return $this->services['root'] = new \stdClass($a, $b); + return $container->services['root'] = new \stdClass($a, $b); } /** @@ -731,15 +741,15 @@ protected function getRootService() * * @return \stdClass */ - protected function getSubscriberService() + protected static function getSubscriberService($container) { - $a = ($this->services['manager'] ?? $this->getManagerService()); + $a = ($container->services['manager'] ?? self::getManagerService($container)); - if (isset($this->services['subscriber'])) { - return $this->services['subscriber']; + if (isset($container->services['subscriber'])) { + return $container->services['subscriber']; } - return $this->services['subscriber'] = new \stdClass($a); + return $container->services['subscriber'] = new \stdClass($a); } /** @@ -747,15 +757,15 @@ protected function getSubscriberService() * * @return \stdClass */ - protected function getBar6Service() + protected static function getBar6Service($container) { - $a = ($this->services['foo6'] ?? $this->getFoo6Service()); + $a = ($container->services['foo6'] ?? self::getFoo6Service($container)); - if (isset($this->privates['bar6'])) { - return $this->privates['bar6']; + if (isset($container->privates['bar6'])) { + return $container->privates['bar6']; } - return $this->privates['bar6'] = new \stdClass($a); + return $container->privates['bar6'] = new \stdClass($a); } /** @@ -763,11 +773,11 @@ protected function getBar6Service() * * @return \stdClass */ - protected function getLevel5Service() + protected static function getLevel5Service($container) { $a = new \Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCalls(); - $this->privates['level5'] = $instance = new \stdClass($a); + $container->privates['level5'] = $instance = new \stdClass($a); $a->call($instance); @@ -779,9 +789,9 @@ protected function getLevel5Service() * * @return \stdClass */ - protected function getMailerInline_MailerService() + protected static function getMailerInline_MailerService($container) { - return $this->privates['mailer_inline.mailer'] = new \stdClass(($this->services['mailer_inline.transport_factory'] ?? $this->getMailerInline_TransportFactoryService())->create()); + return $container->privates['mailer_inline.mailer'] = new \stdClass(($container->services['mailer_inline.transport_factory'] ?? self::getMailerInline_TransportFactoryService($container))->create()); } /** @@ -789,14 +799,14 @@ protected function getMailerInline_MailerService() * * @return \stdClass */ - protected function getManager4Service($lazyLoad = true) + protected static function getManager4Service($container, $lazyLoad = true) { - $a = ($this->services['connection4'] ?? $this->getConnection4Service()); + $a = ($container->services['connection4'] ?? self::getConnection4Service($container)); - if (isset($this->privates['manager4'])) { - return $this->privates['manager4']; + if (isset($container->privates['manager4'])) { + return $container->privates['manager4']; } - return $this->privates['manager4'] = new \stdClass($a); + return $container->privates['manager4'] = new \stdClass($a); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php index 740c006687a65..41e1be5a93b20 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -43,11 +45,11 @@ public function isCompiled(): bool * * @return \BarClass */ - protected function getBarService() + protected static function getBarService($container) { - $this->services['bar'] = $instance = new \BarClass(); + $container->services['bar'] = $instance = new \BarClass(); - $instance->setBaz($this->parameters['array_1'], $this->parameters['array_2'], '%array_1%', $this->parameters['array_1']); + $instance->setBaz($container->parameters['array_1'], $container->parameters['array_2'], '%array_1%', $container->parameters['array_1']); return $instance; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php index c7428f14f420b..8f64e9292ca0e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_Base64Parameters extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -77,8 +79,9 @@ public function getParameterBag(): ParameterBagInterface private function getDynamicParameter(string $name) { + $container = $this; $value = match ($name) { - 'hello' => $this->getEnv('base64:foo'), + 'hello' => $container->getEnv('base64:foo'), default => throw new ParameterNotFoundException($name), }; $this->loadedDynamicParameters[$name] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_closure_argument_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_closure_argument_compiled.php index ae06f37899d04..ca6262cb29d17 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_closure_argument_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_closure_argument_compiled.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'foo' => 'getFooService', @@ -43,9 +45,9 @@ public function isCompiled(): bool * * @return \Foo */ - protected function getFooService() + protected static function getFooService($container) { - return $this->services['foo'] = new \Foo(); + return $container->services['foo'] = new \Foo(); } /** @@ -53,10 +55,14 @@ protected function getFooService() * * @return \Bar */ - protected function getServiceClosureService() + protected static function getServiceClosureService($container) { - return $this->services['service_closure'] = new \Bar(#[\Closure(name: 'foo', class: 'Foo')] function () { - return ($this->services['foo'] ??= new \Foo()); + $containerRef = $container->ref; + + return $container->services['service_closure'] = new \Bar(#[\Closure(name: 'foo', class: 'Foo')] static function () use ($containerRef) { + $container = $containerRef->get(); + + return ($container->services['foo'] ??= new \Foo()); }); } @@ -65,9 +71,13 @@ protected function getServiceClosureService() * * @return \Bar */ - protected function getServiceClosureInvalidService() + protected static function getServiceClosureInvalidService($container) { - return $this->services['service_closure_invalid'] = new \Bar(function () { + $containerRef = $container->ref; + + return $container->services['service_closure_invalid'] = new \Bar(static function () use ($containerRef) { + $container = $containerRef->get(); + return NULL; }); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_csv_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_csv_env.php index 103f1dc7cc22a..dc12622f95c41 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_csv_env.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_csv_env.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_CsvParameters extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -77,8 +79,9 @@ public function getParameterBag(): ParameterBagInterface private function getDynamicParameter(string $name) { + $container = $this; $value = match ($name) { - 'hello' => $this->getEnv('csv:foo'), + 'hello' => $container->getEnv('csv:foo'), default => throw new ParameterNotFoundException($name), }; $this->loadedDynamicParameters[$name] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php index 867cda7e7a289..caf9b2f6c2310 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'bar' => 'getBarService', @@ -49,10 +51,12 @@ protected function createProxy($class, \Closure $factory) * * @return \stdClass */ - protected function getBarService($lazyLoad = true) + protected static function getBarService($container, $lazyLoad = true) { + $containerRef = $container->ref; + if (true === $lazyLoad) { - return $this->services['bar'] = $this->createProxy('stdClassGhost5a8a5eb', fn () => \stdClassGhost5a8a5eb::createLazyGhost($this->getBarService(...))); + return $container->services['bar'] = $container->createProxy('stdClassGhost5a8a5eb', static fn () => \stdClassGhost5a8a5eb::createLazyGhost(static fn ($proxy) => self::getBarService($containerRef->get(), $proxy))); } return $lazyLoad; @@ -63,10 +67,12 @@ protected function getBarService($lazyLoad = true) * * @return \stdClass */ - protected function getBazService($lazyLoad = true) + protected static function getBazService($container, $lazyLoad = true) { + $containerRef = $container->ref; + if (true === $lazyLoad) { - return $this->services['baz'] = $this->createProxy('stdClassProxy5a8a5eb', fn () => \stdClassProxy5a8a5eb::createLazyProxy(fn () => $this->getBazService(false))); + return $container->services['baz'] = $container->createProxy('stdClassProxy5a8a5eb', static fn () => \stdClassProxy5a8a5eb::createLazyProxy(static fn () => self::getBazService($containerRef->get(), false))); } return \foo_bar(); @@ -77,10 +83,12 @@ protected function getBazService($lazyLoad = true) * * @return \stdClass */ - protected function getBuzService($lazyLoad = true) + protected static function getBuzService($container, $lazyLoad = true) { + $containerRef = $container->ref; + if (true === $lazyLoad) { - return $this->services['buz'] = $this->createProxy('stdClassProxy5a8a5eb', fn () => \stdClassProxy5a8a5eb::createLazyProxy(fn () => $this->getBuzService(false))); + return $container->services['buz'] = $container->createProxy('stdClassProxy5a8a5eb', static fn () => \stdClassProxy5a8a5eb::createLazyProxy(static fn () => self::getBuzService($containerRef->get(), false))); } return \foo_bar(); @@ -91,10 +99,12 @@ protected function getBuzService($lazyLoad = true) * * @return \stdClass */ - protected function getFooService($lazyLoad = true) + protected static function getFooService($container, $lazyLoad = true) { + $containerRef = $container->ref; + if (true === $lazyLoad) { - return $this->services['foo'] = $this->createProxy('stdClassGhost5a8a5eb', fn () => \stdClassGhost5a8a5eb::createLazyGhost($this->getFooService(...))); + return $container->services['foo'] = $container->createProxy('stdClassGhost5a8a5eb', static fn () => \stdClassGhost5a8a5eb::createLazyGhost(static fn ($proxy) => self::getFooService($containerRef->get(), $proxy))); } return $lazyLoad; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php index 8c8cc1e30ce2d..4b6c1e3936440 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_Deep_Graph extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'bar' => 'getBarService', @@ -48,11 +50,11 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getBarService() + protected static function getBarService($container) { - $this->services['bar'] = $instance = new \stdClass(); + $container->services['bar'] = $instance = new \stdClass(); - $instance->p5 = new \stdClass(($this->services['foo'] ?? $this->getFooService())); + $instance->p5 = new \stdClass(($container->services['foo'] ?? self::getFooService($container))); return $instance; } @@ -62,12 +64,12 @@ protected function getBarService() * * @return \Symfony\Component\DependencyInjection\Tests\Dumper\FooForDeepGraph */ - protected function getFooService() + protected static function getFooService($container) { - $a = ($this->services['bar'] ?? $this->getBarService()); + $a = ($container->services['bar'] ?? self::getBarService($container)); - if (isset($this->services['foo'])) { - return $this->services['foo']; + if (isset($container->services['foo'])) { + return $container->services['foo']; } $b = new \stdClass(); @@ -76,6 +78,6 @@ protected function getFooService() $b->p2 = $c; - return $this->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\FooForDeepGraph($a, $b); + return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\FooForDeepGraph($a, $b); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_default_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_default_env.php index 1f7f4967b4588..39e0c7f7570b1 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_default_env.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_default_env.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_DefaultParameters extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -79,10 +81,11 @@ public function getParameterBag(): ParameterBagInterface private function getDynamicParameter(string $name) { + $container = $this; $value = match ($name) { - 'fallback_env' => $this->getEnv('foobar'), - 'hello' => $this->getEnv('default:fallback_param:bar'), - 'hello-bar' => $this->getEnv('default:fallback_env:key:baz:json:foo'), + 'fallback_env' => $container->getEnv('foobar'), + 'hello' => $container->getEnv('default:fallback_param:bar'), + 'hello-bar' => $container->getEnv('default:fallback_env:key:baz:json:foo'), default => throw new ParameterNotFoundException($name), }; $this->loadedDynamicParameters[$name] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php index 1fa0137e41cd3..3673a1cdfe1fe 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -52,9 +54,9 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getBarService() + protected static function getBarService($container) { - return $this->services['bar'] = new \stdClass(($this->privates['bar_%env(BAR)%'] ??= new \stdClass())); + return $container->services['bar'] = new \stdClass(($container->privates['bar_%env(BAR)%'] ??= new \stdClass())); } /** @@ -62,9 +64,9 @@ protected function getBarService() * * @return \stdClass */ - protected function getFooService() + protected static function getFooService($container) { - return $this->services['foo'] = new \stdClass(($this->privates['bar_%env(BAR)%'] ??= new \stdClass()), ['baz_'.$this->getEnv('string:BAR') => new \stdClass()]); + return $container->services['foo'] = new \stdClass(($container->privates['bar_%env(BAR)%'] ??= new \stdClass()), ['baz_'.$container->getEnv('string:BAR') => new \stdClass()]); } public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php index 861aecbbf92d9..82409d438413e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Errored_Definition extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -90,11 +92,11 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getBARService() + protected static function getBARService($container) { - $this->services['BAR'] = $instance = new \stdClass(); + $container->services['BAR'] = $instance = new \stdClass(); - $instance->bar = ($this->services['bar'] ?? $this->getBar3Service()); + $instance->bar = ($container->services['bar'] ?? self::getBar3Service($container)); return $instance; } @@ -104,9 +106,9 @@ protected function getBARService() * * @return \stdClass */ - protected function getBAR2Service() + protected static function getBAR2Service($container) { - return $this->services['BAR2'] = new \stdClass(); + return $container->services['BAR2'] = new \stdClass(); } /** @@ -114,9 +116,9 @@ protected function getBAR2Service() * * @return \Bar */ - protected function getAServiceService() + protected static function getAServiceService($container) { - return $this->services['a_service'] = ($this->privates['a_factory'] ??= new \Bar())->getBar(); + return $container->services['a_service'] = ($container->privates['a_factory'] ??= new \Bar())->getBar(); } /** @@ -124,9 +126,9 @@ protected function getAServiceService() * * @return \Bar */ - protected function getBServiceService() + protected static function getBServiceService($container) { - return $this->services['b_service'] = ($this->privates['a_factory'] ??= new \Bar())->getBar(); + return $container->services['b_service'] = ($container->privates['a_factory'] ??= new \Bar())->getBar(); } /** @@ -134,11 +136,11 @@ protected function getBServiceService() * * @return \Bar\FooClass */ - protected function getBar3Service() + protected static function getBar3Service($container) { - $a = ($this->services['foo.baz'] ?? $this->getFoo_BazService()); + $a = ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); - $this->services['bar'] = $instance = new \Bar\FooClass('foo', $a, 'foo_bar'); + $container->services['bar'] = $instance = new \Bar\FooClass('foo', $a, 'foo_bar'); $a->configure($instance); @@ -150,9 +152,9 @@ protected function getBar3Service() * * @return \stdClass */ - protected function getBar22Service() + protected static function getBar22Service($container) { - return $this->services['bar2'] = new \stdClass(); + return $container->services['bar2'] = new \stdClass(); } /** @@ -160,11 +162,11 @@ protected function getBar22Service() * * @return \Baz */ - protected function getBazService() + protected static function getBazService($container) { - $this->services['baz'] = $instance = new \Baz(); + $container->services['baz'] = $instance = new \Baz(); - $instance->setFoo(($this->services['foo_with_inline'] ?? $this->getFooWithInlineService())); + $instance->setFoo(($container->services['foo_with_inline'] ?? self::getFooWithInlineService($container))); return $instance; } @@ -174,12 +176,12 @@ protected function getBazService() * * @return \stdClass */ - protected function getConfiguredServiceService() + protected static function getConfiguredServiceService($container) { - $this->services['configured_service'] = $instance = new \stdClass(); + $container->services['configured_service'] = $instance = new \stdClass(); $a = new \ConfClass(); - $a->setFoo(($this->services['baz'] ?? $this->getBazService())); + $a->setFoo(($container->services['baz'] ?? self::getBazService($container))); $a->configureStdClass($instance); @@ -191,9 +193,9 @@ protected function getConfiguredServiceService() * * @return \stdClass */ - protected function getConfiguredServiceSimpleService() + protected static function getConfiguredServiceSimpleService($container) { - $this->services['configured_service_simple'] = $instance = new \stdClass(); + $container->services['configured_service_simple'] = $instance = new \stdClass(); (new \ConfClass('bar'))->configureStdClass($instance); @@ -205,9 +207,9 @@ protected function getConfiguredServiceSimpleService() * * @return \stdClass */ - protected function getDecoratorServiceService() + protected static function getDecoratorServiceService($container) { - return $this->services['decorator_service'] = new \stdClass(); + return $container->services['decorator_service'] = new \stdClass(); } /** @@ -215,9 +217,9 @@ protected function getDecoratorServiceService() * * @return \stdClass */ - protected function getDecoratorServiceWithNameService() + protected static function getDecoratorServiceWithNameService($container) { - return $this->services['decorator_service_with_name'] = new \stdClass(); + return $container->services['decorator_service_with_name'] = new \stdClass(); } /** @@ -227,11 +229,11 @@ protected function getDecoratorServiceWithNameService() * * @deprecated Since vendor/package 1.1: The "deprecated_service" service is deprecated. You should stop using it, as it will be removed in the future. */ - protected function getDeprecatedServiceService() + protected static function getDeprecatedServiceService($container) { trigger_deprecation('vendor/package', '1.1', 'The "deprecated_service" service is deprecated. You should stop using it, as it will be removed in the future.'); - return $this->services['deprecated_service'] = new \stdClass(); + return $container->services['deprecated_service'] = new \stdClass(); } /** @@ -239,9 +241,9 @@ protected function getDeprecatedServiceService() * * @return \Bar */ - protected function getFactoryServiceService() + protected static function getFactoryServiceService($container) { - return $this->services['factory_service'] = ($this->services['foo.baz'] ?? $this->getFoo_BazService())->getInstance(); + return $container->services['factory_service'] = ($container->services['foo.baz'] ?? self::getFoo_BazService($container))->getInstance(); } /** @@ -249,9 +251,9 @@ protected function getFactoryServiceService() * * @return \Bar */ - protected function getFactoryServiceSimpleService() + protected static function getFactoryServiceSimpleService($container) { - return $this->services['factory_service_simple'] = $this->getFactorySimpleService()->getInstance(); + return $container->services['factory_service_simple'] = self::getFactorySimpleService($container)->getInstance(); } /** @@ -259,16 +261,16 @@ protected function getFactoryServiceSimpleService() * * @return \Bar\FooClass */ - protected function getFooService() + protected static function getFooService($container) { - $a = ($this->services['foo.baz'] ?? $this->getFoo_BazService()); + $a = ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); - $this->services['foo'] = $instance = \Bar\FooClass::getInstance('foo', $a, ['bar' => 'foo is bar', 'foobar' => 'bar'], true, $this); + $container->services['foo'] = $instance = \Bar\FooClass::getInstance('foo', $a, ['bar' => 'foo is bar', 'foobar' => 'bar'], true, $container); $instance->foo = 'bar'; $instance->moo = $a; $instance->qux = ['bar' => 'foo is bar', 'foobar' => 'bar']; - $instance->setBar(($this->services['bar'] ?? $this->getBar3Service())); + $instance->setBar(($container->services['bar'] ?? self::getBar3Service($container))); $instance->initialize(); sc_configure($instance); @@ -280,9 +282,9 @@ protected function getFooService() * * @return \BazClass */ - protected function getFoo_BazService() + protected static function getFoo_BazService($container) { - $this->services['foo.baz'] = $instance = \BazClass::getInstance(); + $container->services['foo.baz'] = $instance = \BazClass::getInstance(); \BazClass::configureStatic1($instance); @@ -294,13 +296,13 @@ protected function getFoo_BazService() * * @return \Bar\FooClass */ - protected function getFooBarService() + protected static function getFooBarService($container) { - $this->factories['foo_bar'] = function () { - return new \Bar\FooClass(($this->services['deprecated_service'] ?? $this->getDeprecatedServiceService())); + $container->factories['foo_bar'] = static function ($container) { + return new \Bar\FooClass(($container->services['deprecated_service'] ?? self::getDeprecatedServiceService($container))); }; - return $this->factories['foo_bar'](); + return $container->factories['foo_bar']($container); } /** @@ -308,13 +310,13 @@ protected function getFooBarService() * * @return \Foo */ - protected function getFooWithInlineService() + protected static function getFooWithInlineService($container) { - $this->services['foo_with_inline'] = $instance = new \Foo(); + $container->services['foo_with_inline'] = $instance = new \Foo(); $a = new \Bar(); $a->pub = 'pub'; - $a->setBaz(($this->services['baz'] ?? $this->getBazService())); + $a->setBaz(($container->services['baz'] ?? self::getBazService($container))); $instance->setBar($a); @@ -326,12 +328,16 @@ protected function getFooWithInlineService() * * @return \LazyContext */ - protected function getLazyContextService() + protected static function getLazyContextService($container) { - return $this->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function () { - yield 'k1' => ($this->services['foo.baz'] ?? $this->getFoo_BazService()); - yield 'k2' => $this; - }, 2), new RewindableGenerator(function () { + $containerRef = $container->ref; + + return $container->services['lazy_context'] = new \LazyContext(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 'k1' => ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); + yield 'k2' => $container; + }, 2), new RewindableGenerator(static function () { return new \EmptyIterator(); }, 0)); } @@ -341,11 +347,15 @@ protected function getLazyContextService() * * @return \LazyContext */ - protected function getLazyContextIgnoreInvalidRefService() + protected static function getLazyContextIgnoreInvalidRefService($container) { - return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function () { - yield 0 => ($this->services['foo.baz'] ?? $this->getFoo_BazService()); - }, 1), new RewindableGenerator(function () { + $containerRef = $container->ref; + + return $container->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new R 10000 ewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 0 => ($container->services['foo.baz'] ?? self::getFoo_BazService($container)); + }, 1), new RewindableGenerator(static function () { return new \EmptyIterator(); }, 0)); } @@ -355,15 +365,15 @@ protected function getLazyContextIgnoreInvalidRefService() * * @return \Bar\FooClass */ - protected function getMethodCall1Service() + protected static function getMethodCall1Service($container) { include_once '%path%foo.php'; - $this->services['method_call1'] = $instance = new \Bar\FooClass(); + $container->services['method_call1'] = $instance = new \Bar\FooClass(); - $instance->setBar(($this->services['foo'] ?? $this->getFooService())); + $instance->setBar(($container->services['foo'] ?? self::getFooService($container))); $instance->setBar(NULL); - $instance->setBar((($this->services['foo'] ?? $this->getFooService())->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default")))); + $instance->setBar((($container->services['foo'] ?? self::getFooService($container))->foo() . (($container->hasParameter("foo")) ? ($container->getParameter("foo")) : ("default")))); return $instance; } @@ -373,12 +383,12 @@ protected function getMethodCall1Service() * * @return \FooBarBaz */ - protected function getNewFactoryServiceService() + protected static function getNewFactoryServiceService($container) { $a = new \FactoryClass(); $a->foo = 'bar'; - $this->services['new_factory_service'] = $instance = $a->getInstance(); + $container->services['new_factory_service'] = $instance = $a->getInstance(); $instance->foo = 'bar'; @@ -390,9 +400,9 @@ protected function getNewFactoryServiceService() * * @return \stdClass */ - protected function getPreloadSidekickService() + protected static function getPreloadSidekickService($container) { - return $this->services['preload_sidekick'] = new \stdClass(); + return $container->services['preload_sidekick'] = new \stdClass(); } /** @@ -400,9 +410,9 @@ protected function getPreloadSidekickService() * * @return \stdClass */ - protected function getRuntimeErrorService() + protected static function getRuntimeErrorService($container) { - return $this->services['runtime_error'] = new \stdClass(throw new RuntimeException('Service "errored_definition" is broken.')); + return $container->services['runtime_error'] = new \stdClass(throw new RuntimeException('Service "errored_definition" is broken.')); } /** @@ -410,9 +420,9 @@ protected function getRuntimeErrorService() * * @return \Bar\FooClass */ - protected function getServiceFromStaticMethodService() + protected static function getServiceFromStaticMethodService($container) { - return $this->services['service_from_static_method'] = \Bar\FooClass::getInstance(); + return $container->services['service_from_static_method'] = \Bar\FooClass::getInstance(); } /** @@ -420,11 +430,15 @@ protected function getServiceFromStaticMethodService() * * @return \Bar */ - protected function getTaggedIteratorService() + protected static function getTaggedIteratorService($container) { - return $this->services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () { - yield 0 => ($this->services['foo'] ?? $this->getFooService()); - yield 1 => ($this->privates['tagged_iterator_foo'] ??= new \Bar()); + $containerRef = $container->ref; + + return $container->services['tagged_iterator'] = new \Bar(new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + yield 0 => ($container->services['foo'] ?? self::getFooService($container)); + yield 1 => ($container->privates['tagged_iterator_foo'] ??= new \Bar()); }, 2)); } @@ -435,7 +449,7 @@ protected function getTaggedIteratorService() * * @deprecated Since vendor/package 1.1: The "factory_simple" service is deprecated. You should stop using it, as it will be removed in the future. */ - protected function getFactorySimpleService() + protected static function getFactorySimpleService($container) { trigger_deprecation('vendor/package', '1.1', 'The "factory_simple" service is deprecated. You should stop using it, as it will be removed in the future.'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php index d0509ad594369..f184f64dca43a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\ParentNotExists' => 'getParentNotExistsService', @@ -27,7 +29,7 @@ public function __construct() $this->aliases = []; - $this->privates['service_container'] = function () { + $this->privates['service_container'] = static function ($container) { include_once \dirname(__DIR__, 1).'/includes/HotPath/I1.php'; include_once \dirname(__DIR__, 1).'/includes/HotPath/P1.php'; include_once \dirname(__DIR__, 1).'/includes/HotPath/T1.php'; @@ -57,9 +59,9 @@ public function getRemovedIds(): array * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists */ - protected function getParentNotExistsService() + protected static function getParentNotExistsService($container) { - return $this->services['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\ParentNotExists'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists(); + return $container->services['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\ParentNotExists'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists(); } /** @@ -67,9 +69,9 @@ protected function getParentNotExistsService() * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C1 */ - protected function getC1Service() + protected static function getC1Service($container) { - return $this->services['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C1'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C1(); + return $container->services['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C1'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C1(); } /** @@ -77,11 +79,11 @@ protected function getC1Service() * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2 */ - protected function getC2Service() + protected static function getC2Service($container) { include_once \dirname(__DIR__, 1).'/includes/HotPath/C2.php'; include_once \dirname(__DIR__, 1).'/includes/HotPath/C3.php'; - return $this->services['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2(new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3()); + return $container->services['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2(new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3()); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_self_ref.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_self_ref.php index ed8c7c17e9fcc..4ccee1b0c2bc4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_self_ref.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_self_ref.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_Inline_Self_Ref extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'App\\Foo' => 'getFooService', @@ -41,14 +43,14 @@ public function isCompiled(): bool * * @return \App\Foo */ - protected function getFooService() + protected static function getFooService($container) { $a = new \App\Bar(); $b = new \App\Baz($a); $b->bar = $a; - $this->services['App\\Foo'] = $instance = new \App\Foo($b); + $container->services['App\\Foo'] = $instance = new \App\Foo($b); $a->foo = $instance; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_json_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_json_env.php index 0b4337e2dfc06..35aa89cd07e72 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_json_env.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_json_env.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_JsonParameters extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -78,9 +80,10 @@ public function getParameterBag(): ParameterBagInterface private function getDynamicParameter(string $name) { + $container = $this; $value = match ($name) { - 'hello' => $this->getEnv('json:foo'), - 'hello-bar' => $this->getEnv('json:bar'), + 'hello' => $container->getEnv('json:foo'), + 'hello-bar' => $container->getEnv('json:bar'), default => throw new ParameterNotFoundException($name), }; $this->loadedDynamicParameters[$name] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php index cf3abe06e3f00..4239d82f1e721 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'bar_service' => 'getBarServiceService', @@ -58,9 +60,9 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getBarServiceService() + protected static function getBarServiceService($container) { - return $this->services['bar_service'] = new \stdClass(($this->privates['baz_service'] ??= new \stdClass())); + return $container->services['bar_service'] = new \stdClass(($container->privates['baz_service'] ??= new \stdClass())); } /** @@ -68,13 +70,21 @@ protected function getBarServiceService() * * @return \Symfony\Component\DependencyInjection\ServiceLocator */ - protected function getFooServiceService() + protected static function getFooServiceService($container) { - return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\ServiceLocator(['bar' => #[\Closure(name: 'bar_service', class: 'stdClass')] function () { - return ($this->services['bar_service'] ?? $this->getBarServiceService()); - }, 'baz' => #[\Closure(name: 'baz_service', class: 'stdClass')] function (): \stdClass { - return ($this->privates['baz_service'] ??= new \stdClass()); - }, 'nil' => function () { + $containerRef = $container->ref; + + return $container->services['foo_service'] = new \Symfony\Component\DependencyInjection\ServiceLocator(['bar' => #[\Closure(name: 'bar_service', class: 'stdClass')] static function () use ($containerRef) { + $container = $containerRef->get(); + + return ($container->services['bar_service'] ?? self::getBarServiceService($container)); + }, 'baz' => #[\Closure(name: 'baz_service', class: 'stdClass')] static function () use ($containerRef): \stdClass { + $container = $containerRef->get(); + + return ($container->privates['baz_service'] ??= new \stdClass()); + }, 'nil' => static function () use ($containerRef) { + $container = $containerRef->get(); + return NULL; }]); } @@ -84,9 +94,9 @@ protected function getFooServiceService() * * @return \stdClass */ - protected function getTranslator_Loader1Service() + protected static function getTranslator_Loader1Service($container) { - return $this->services['translator.loader_1'] = new \stdClass(); + return $container->services['translator.loader_1'] = new \stdClass(); } /** @@ -94,9 +104,9 @@ protected function getTranslator_Loader1Service() * * @return \stdClass */ - protected function getTranslator_Loader2Service() + protected static function getTranslator_Loader2Service($container) { - return $this->services['translator.loader_2'] = new \stdClass(); + return $container->services['translator.loader_2'] = new \stdClass(); } /** @@ -104,9 +114,9 @@ protected function getTranslator_Loader2Service() * * @return \stdClass */ - protected function getTranslator_Loader3Service() + protected static function getTranslator_Loader3Service($container) { - return $this->services['translator.loader_3'] = new \stdClass(); + return $container->services['translator.loader_3'] = new \stdClass(); } /** @@ -114,10 +124,14 @@ protected function getTranslator_Loader3Service() * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator */ - protected function getTranslator1Service() + protected static function getTranslator1Service($container) { - return $this->services['translator_1'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(['translator.loader_1' => #[\Closure(name: 'translator.loader_1', class: 'stdClass')] function () { - return ($this->services['translator.loader_1'] ??= new \stdClass()); + $containerRef = $container->ref; + + return $container->services['translator_1'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(['translator.loader_1' => #[\Closure(name: 'translator.loader_1', class: 'stdClass')] static function () use ($containerRef) { + $container = $containerRef->get(); + + return ($container->services['translator.loader_1'] ??= new \stdClass()); }])); } @@ -126,13 +140,17 @@ protected function getTranslator1Service() * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator */ - protected function getTranslator2Service() + protected static function getTranslator2Service($container) { - $this->services['translator_2'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(['translator.loader_2' => #[\Closure(name: 'translator.loader_2', class: 'stdClass')] function () { - return ($this->services['translator.loader_2'] ??= new \stdClass()); + $containerRef = $container->ref; + + $container->services['translator_2'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(['translator.loader_2' => #[\Closure(name: 'translator.loader_2', class: 'stdClass')] static function () use ($containerRef) { + $container = $containerRef->get(); + + return ($container->services['translator.loader_2'] ??= new \stdClass()); }])); - $instance->addResource('db', ($this->services['translator.loader_2'] ??= new \stdClass()), 'nl'); + $instance->addResource('db', ($container->services['translator.loader_2'] ??= new \stdClass()), 'nl'); return $instance; } @@ -142,13 +160,17 @@ protected function getTranslator2Service() * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator */ - protected function getTranslator3Service() + protected static function getTranslator3Service($container) { - $this->services['translator_3'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(['translator.loader_3' => #[\Closure(name: 'translator.loader_3', class: 'stdClass')] function () { - return ($this->services['translator.loader_3'] ??= new \stdClass()); + $containerRef = $container->ref; + + $container->services['translator_3'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(['translator.loader_3' => #[\Closure(name: 'translator.loader_3', class: 'stdClass')] static function () use ($containerRef) { + $container = $containerRef->get(); + + return ($container->services['translator.loader_3'] ??= new \stdClass()); }])); - $a = ($this->services['translator.loader_3'] ??= new \stdClass()); + $a = ($container->services['translator.loader_3'] ??= new \stdClass()); $instance->addResource('db', $a, 'nl'); $instance->addResource('db', $a, 'en'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_new_in_initializer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_new_in_initializer.php index 0928b5c6f2c1c..238fabbae8c6f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_new_in_initializer.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_new_in_initializer.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'foo' => 'getFooService', @@ -41,8 +43,8 @@ public function isCompiled(): bool * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\NewInInitializer */ - protected function getFooService() + protected static function getFooService($container) { - return $this->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\NewInInitializer(bar: 234); + return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\NewInInitializer(bar: 234); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_duplicates.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_duplicates.php index 3802fc28ffc1b..4ce242bb265d8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_duplicates.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_duplicates.php @@ -15,11 +15,13 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; protected \Closure $getService; public function __construct() { - $this->getService = $this->getService(...); + $containerRef = $this->ref = \WeakReference::create($this); + $this->getService = static function () use ($containerRef) { return $containerRef->get()->getService(...\func_get_args()); }; $this->services = $this->privates = []; $this->methodMap = [ 'bar' => 'getBarService', @@ -52,9 +54,9 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getBarService() + protected static function getBarService($container) { - return $this->services['bar'] = new \stdClass((new \stdClass()), (new \stdClass())); + return $container->services['bar'] = new \stdClass((new \stdClass()), (new \stdClass())); } /** @@ -62,9 +64,9 @@ protected function getBarService() * * @return \stdClass */ - protected function getBazService() + protected static function getBazService($container) { - return $this->services['baz'] = new \stdClass(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($this->getService, [ + return $container->services['baz'] = new \stdClass(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService, [ 'foo' => [false, 'foo', 'getFooService', false], ], [ 'foo' => '?', @@ -76,12 +78,12 @@ protected function getBazService() * * @return \stdClass */ - protected function getFooService() + protected static function getFooService($container) { - $this->factories['service_container']['foo'] = function () { + $container->factories['service_container']['foo'] = static function ($container) { return new \stdClass(); }; - return $this->factories['service_container']['foo'](); + return $container->factories['service_container']['foo']($container); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php index 001d7746da3bb..d00f210156e34 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'bar' => 'getBarService', @@ -53,9 +55,9 @@ protected function createProxy($class, \Closure $factory) * * @return \stdClass */ - protected function getBarService() + protected static function getBarService($container) { - return $this->services['bar'] = new \stdClass((isset($this->factories['service_container']['foo']) ? $this->factories['service_container']['foo']() : $this->getFooService())); + return $container->services['bar'] = new \stdClass((isset($container->factories['service_container']['foo']) ? $container->factories['service_container']['foo']($container) : self::getFooService($container))); } /** @@ -63,9 +65,11 @@ protected function getBarService() * * @return \stdClass */ - protected function getFooService($lazyLoad = true) + protected static function getFooService($container, $lazyLoad = true) { - $this->factories['service_container']['foo'] ??= $this->getFooService(...); + $containerRef = $container->ref; + + $container->factories['service_container']['foo'] ??= self::getFooService(...); // lazy factory for stdClass diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt index a4e8807c71039..0594c76789555 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt @@ -19,10 +19,12 @@ class getNonSharedFooService extends ProjectServiceContainer */ public static function do($container, $lazyLoad = true) { - $container->factories['non_shared_foo'] ??= fn () => self::do($container); + $containerRef = $container->ref; + + $container->factories['non_shared_foo'] ??= self::do(...); if (true === $lazyLoad) { - return $container->createProxy('FooLazyClassGhostF814e3a', fn () => \FooLazyClassGhostF814e3a::createLazyGhost(fn ($proxy) => self::do($container, $proxy))); + return $container->createProxy('FooLazyClassGhostF814e3a', static fn () => \FooLazyClassGhostF814e3a::createLazyGhost(static fn ($proxy) => self::do($containerRef->get(), $proxy))); } static $include = true; @@ -79,9 +81,11 @@ class ProjectServiceContainer extends Container protected $targetDir; protected $parameters = []; private $buildParameters; + protected readonly \WeakReference $ref; public function __construct(array $buildParameters = [], $containerDir = __DIR__) { + $this->ref = \WeakReference::create($this); $this->buildParameters = $buildParameters; $this->containerDir = $containerDir; $this->targetDir = \dirname($containerDir); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php index 7d584eef41063..b318bb5cfec61 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'bar' => 'getBarService', @@ -53,9 +55,9 @@ protected function createProxy($class, \Closure $factory) * * @return \stdClass */ - protected function getBarService() + protected static function getBarService($container) { - return $this->services['bar'] = new \stdClass((isset($this->factories['service_container']['foo']) ? $this->factories['service_container']['foo']() : $this->getFooService())); + return $container->services['bar'] = new \stdClass((isset($container->factories['service_container']['foo']) ? $container->factories['service_container']['foo']($container) : self::getFooService($container))); } /** @@ -63,12 +65,14 @@ protected function getBarService() * * @return \stdClass */ - protected function getFooService($lazyLoad = true) + protected static function getFooService($container, $lazyLoad = true) { - $this->factories['service_container']['foo'] ??= $this->getFooService(...); + $containerRef = $container->ref; + + $container->factories['service_container']['foo'] ??= self::getFooService(...); if (true === $lazyLoad) { - return $this->createProxy('stdClassGhost5a8a5eb', fn () => \stdClassGhost5a8a5eb::createLazyGhost($this->getFooService(...))); + return $container->createProxy('stdClassGhost5a8a5eb', static fn () => \stdClassGhost5a8a5eb::createLazyGhost(static fn ($proxy) => self::getFooService($containerRef->get(), $proxy))); } return $lazyLoad; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php index 3f3470a7b85db..5d627cb89f0db 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'bar_service' => 'getBarServiceService', @@ -49,9 +51,9 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getBarServiceService() + protected static function getBarServiceService($container) { - return $this->services['bar_service'] = new \stdClass(($this->privates['baz_service'] ??= new \stdClass())); + return $container->services['bar_service'] = new \stdClass(($container->privates['baz_service'] ??= new \stdClass())); } /** @@ -59,8 +61,8 @@ protected function getBarServiceService() * * @return \stdClass */ - protected function getFooServiceService() + protected static function getFooServiceService($container) { - return $this->services['foo_service'] = new \stdClass(($this->privates['baz_service'] ??= new \stdClass())); + return $container->services['foo_service'] = new \stdClass(($container->privates['baz_service'] ??= new \stdClass())); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php index 0bd20277b273d..6badcf4d0a48a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'public_foo' => 'getPublicFooService', @@ -49,8 +51,8 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getPublicFooService() + protected static function getPublicFooService($container) { - return $this->services['public_foo'] = new \stdClass((new \stdClass())->bar); + return $container->services['public_foo'] = new \stdClass((new \stdClass())->bar); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_query_string_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_query_string_env.php index 738722b7549b3..d3dc84d68c7c4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_query_string_env.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_query_string_env.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_QueryStringParameters extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -77,8 +79,9 @@ public function getParameterBag(): ParameterBagInterface private function getDynamicParameter(string $name) { + $container = $this; $value = match ($name) { - 'hello' => $this->getEnv('query_string:foo'), + 'hello' => $container->getEnv('query_string:foo'), default => throw new ParameterNotFoundException($name), }; $this->loadedDynamicParameters[$name] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php index 31b003e2eb08b..60700526ead94 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php @@ -15,11 +15,13 @@ class Symfony_DI_PhpDumper_Test_Rot13Parameters extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; protected \Closure $getService; public function __construct() { - $this->getService = $this->getService(...); + $containerRef = $this->ref = \WeakReference::create($this); + $this->getService = static function () use ($containerRef) { return $containerRef->get()->getService(...\func_get_args()); }; $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -53,9 +55,9 @@ public function getRemovedIds(): array * * @return \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor */ - protected function getRot13EnvVarProcessorService() + protected static function getRot13EnvVarProcessorService($container) { - return $this->services['Symfony\\Component\\DependencyInjection\\Tests\\Dumper\\Rot13EnvVarProcessor'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor(); + return $container->services['Symfony\\Component\\DependencyInjection\\Tests\\Dumper\\Rot13EnvVarProcessor'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor(); } /** @@ -63,9 +65,9 @@ protected function getRot13EnvVarProcessorService() * * @return \Symfony\Component\DependencyInjection\ServiceLocator */ - protected function getContainer_EnvVarProcessorsLocatorService() + protected static function getContainer_EnvVarProcessorsLocatorService($container) { - return $this->services['container.env_var_processors_locator'] = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($this->getService, [ + return $container->services['container.env_var_processors_locator'] = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService, [ 'rot13' => ['services', 'Symfony\\Component\\DependencyInjection\\Tests\\Dumper\\Rot13EnvVarProcessor', 'getRot13EnvVarProcessorService', false], ], [ 'rot13' => '?', @@ -114,8 +116,9 @@ public function getParameterBag(): ParameterBagInterface private function getDynamicParameter(string $name) { + $container = $this; $value = match ($name) { - 'hello' => $this->getEnv('rot13:foo'), + 'hello' => $container->getEnv('rot13:foo'), default => throw new ParameterNotFoundException($name), }; $this->loadedDynamicParameters[$name] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_service_locator_argument.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_service_locator_argument.php index c8125acfb0cba..d28800abf0540 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_service_locator_argument.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_service_locator_argument.php @@ -15,11 +15,13 @@ class Symfony_DI_PhpDumper_Service_Locator_Argument extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; protected \Closure $getService; public function __construct() { - $this->getService = $this->getService(...); + $containerRef = $this->ref = \WeakReference::create($this); + $this->getService = static function () use ($containerRef) { return $containerRef->get()->getService(...\func_get_args()); }; $this->services = $this->privates = []; $this->syntheticIds = [ 'foo5' => true, @@ -57,11 +59,11 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getBarService() + protected static function getBarService($container) { - $this->services['bar'] = $instance = new \stdClass(); + $container->services['bar'] = $instance = new \stdClass(); - $instance->locator = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($this->getService, [ + $instance->locator = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService, [ 'foo1' => ['services', 'foo1', 'getFoo1Service', false], 'foo2' => ['privates', 'foo2', 'getFoo2Service', false], 'foo3' => [false, 'foo3', 'getFoo3Service', false], @@ -83,9 +85,9 @@ protected function getBarService() * * @return \stdClass */ - protected function getFoo1Service() + protected static function getFoo1Service($container) { - return $this->services['foo1'] = new \stdClass(); + return $container->services['foo1'] = new \stdClass(); } /** @@ -93,9 +95,9 @@ protected function getFoo1Service() * * @return \stdClass */ - protected function getFoo2Service() + protected static function getFoo2Service($container) { - return $this->privates['foo2'] = new \stdClass(); + return $container->privates['foo2'] = new \stdClass(); } /** @@ -103,13 +105,13 @@ protected function getFoo2Service() * * @return \stdClass */ - protected function getFoo3Service() + protected static function getFoo3Service($container) { - $this->factories['service_container']['foo3'] = function () { + $container->factories['service_container']['foo3'] = static function ($container) { return new \stdClass(); }; - return $this->factories['service_container']['foo3'](); + return $container->factories['service_container']['foo3']($container); } /** @@ -117,7 +119,7 @@ protected function getFoo3Service() * * @return \stdClass */ - protected function getFoo4Service() + protected static function getFoo4Service($container) { throw new RuntimeException('BOOM'); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php index 1928f94a41110..77ed9e02882a4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php @@ -15,11 +15,13 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; protected \Closure $getService; public function __construct() { - $this->getService = $this->getService(...); + $containerRef = $this->ref = \WeakReference::create($this); + $this->getService = static function () use ($containerRef) { return $containerRef->get()->getService(...\func_get_args()); }; $this->services = $this->privates = []; $this->methodMap = [ 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => 'getTestServiceSubscriberService', @@ -57,9 +59,9 @@ public function getRemovedIds(): array * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber */ - protected function getTestServiceSubscriberService() + protected static function getTestServiceSubscriberService($container) { - return $this->services['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber(); + return $container->services['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber(); } /** @@ -67,9 +69,9 @@ protected function getTestServiceSubscriberService() * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber */ - protected function getFooServiceService() + protected static function getFooServiceService($container) { - return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber((new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($this->getService, [ + return $container->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber((new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService, [ 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => ['privates', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition', 'getCustomDefinitionService', false], 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => ['services', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber', 'getTestServiceSubscriberService', false], 'bar' => ['services', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber', 'getTestServiceSubscriberService', false], @@ -81,7 +83,7 @@ protected function getFooServiceService() 'bar' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition', 'baz' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition', 'late_alias' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestDefinition1', - ]))->withContext('foo_service', $this)); + ]))->withContext('foo_service', $container)); } /** @@ -89,9 +91,9 @@ protected function getFooServiceService() * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1 */ - protected function getLateAliasService() + protected static function getLateAliasService($container) { - return $this->services['late_alias'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1(); + return $container->services['late_alias'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1(); } /** @@ -99,8 +101,8 @@ protected function getLateAliasService() * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition */ - protected function getCustomDefinitionService() + protected static function getCustomDefinitionService($container) { - return $this->privates['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition(); + return $container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition(); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_tsantos.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_tsantos.php index 2cf21ad94dbd2..00dab8c875eee 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_tsantos.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_tsantos.php @@ -15,9 +15,11 @@ class ProjectServiceContainer extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'tsantos_serializer' => 'getTsantosSerializerService', @@ -48,7 +50,7 @@ public function getRemovedIds(): array * * @return \TSantos\Serializer\EventEmitterSerializer */ - protected function getTsantosSerializerService() + protected static function getTsantosSerializerService($container) { $a = new \TSantos\Serializer\NormalizerRegistry(); @@ -57,7 +59,7 @@ protected function getTsantosSerializerService() $c = new \TSantos\Serializer\EventDispatcher\EventDispatcher(); $c->addSubscriber(new \TSantos\SerializerBundle\EventListener\StopwatchListener(new \Symfony\Component\Stopwatch\Stopwatch(true))); - $this->services['tsantos_serializer'] = $instance = new \TSantos\Serializer\EventEmitterSerializer(new \TSantos\Serializer\Encoder\JsonEncoder(), $a, $c); + $container->services['tsantos_serializer'] = $instance = new \TSantos\Serializer\EventEmitterSerializer(new \TSantos\Serializer\Encoder\JsonEncoder(), $a, $c); $b->setSerializer($instance); $d = new \TSantos\Serializer\Normalizer\JsonNormalizer(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php index d528f0d9f092d..35b5a3260f008 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_Uninitialized_Reference extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'bar' => 'getBarService', @@ -51,32 +53,44 @@ public function getRemovedIds(): array * * @return \stdClass */ - protected function getBarService() + protected static function getBarService($container) { - $this->services['bar'] = $instance = new \stdClass(); + $containerRef = $container->ref; - $instance->foo1 = ($this->services['foo1'] ?? null); + $container->services['bar'] = $instance = new \stdClass(); + + $instance->foo1 = ($container->services['foo1'] ?? null); $instance->foo2 = null; - $instance->foo3 = ($this->privates['foo3'] ?? null); - $instance->closures = [0 => #[\Closure(name: 'foo1', class: 'stdClass')] function () { - return ($this->services['foo1'] ?? null); - }, 1 => #[\Closure(name: 'foo2')] function () { + $instance->foo3 = ($container->privates['foo3'] ?? null); + $instance->closures = [0 => #[\Closure(name: 'foo1', class: 'stdClass')] static function () use ($containerRef) { + $container = $containerRef->get(); + + return ($container->services['foo1'] ?? null); + }, 1 => #[\Closure(name: 'foo2')] static function () use ($containerRef) { + $container = $containerRef->get(); + return null; - }, 2 => #[\Closure(name: 'foo3', class: 'stdClass')] function () { - return ($this->privates['foo3'] ?? null); + }, 2 => #[\Closure(name: 'foo3', class: 'stdClass')] static function () use ($containerRef) { + $container = $containerRef->get(); + + return ($container->privates['foo3'] ?? null); }]; - $instance->iter = new RewindableGenerator(function () { - if (isset($this->services['foo1'])) { - yield 'foo1' => ($this->services['foo1'] ?? null); + $instance->iter = new RewindableGenerator(static function () use ($containerRef) { + $container = $containerRef->get(); + + if (isset($container->services['foo1'])) { + yield 'foo1' => ($container->services['foo1'] ?? null); } if (false) { yield 'foo2' => null; } - if (isset($this->privates['foo3'])) { - yield 'foo3' => ($this->privates['foo3'] ?? null); + if (isset($container->privates['foo3'])) { + yield 'foo3' => ($container->privates['foo3'] ?? null); } - }, function () { - return 0 + (int) (isset($this->services['foo1'])) + (int) (false) + (int) (isset($this->privates['foo3'])); + }, static function () use ($containerRef) { + $container = $containerRef->get(); + + return 0 + (int) (isset($container->services['foo1'])) + (int) (false) + (int) (isset($container->privates['foo3'])); }); return $instance; @@ -87,11 +101,11 @@ protected function getBarService() * * @return \stdClass */ - protected function getBazService() + protected static function getBazService($container) { - $this->services['baz'] = $instance = new \stdClass(); + $container->services['baz'] = $instance = new \stdClass(); - $instance->foo3 = ($this->privates['foo3'] ??= new \stdClass()); + $instance->foo3 = ($container->privates['foo3'] ??= new \stdClass()); return $instance; } @@ -101,8 +115,8 @@ protected function getBazService() * * @return \stdClass */ - protected function getFoo1Service() + protected static function getFoo1Service($container) { - return $this->services['foo1'] = new \stdClass(); + return $container->services['foo1'] = new \stdClass(); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_unsupported_characters.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_unsupported_characters.php index 3a0f8d7539e94..3473edb993327 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_unsupported_characters.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_unsupported_characters.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_Unsupported_Characters extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -45,9 +47,9 @@ public function isCompiled(): bool * * @return \FooClass */ - protected function getBarService() + protected static function getBarService($container) { - return $this->services['bar$'] = new \FooClass(); + return $container->services['bar$'] = new \FooClass(); } /** @@ -55,9 +57,9 @@ protected function getBarService() * * @return \FooClass */ - protected function getBar2Service() + protected static function getBar2Service($container) { - return $this->services['bar$!'] = new \FooClass(); + return $container->services['bar$!'] = new \FooClass(); } /** @@ -65,9 +67,9 @@ protected function getBar2Service() * * @return \FooClass */ - protected function getFooohnoService() + protected static function getFooohnoService($container) { - return $this->services['foo*/oh-no'] = new \FooClass(); + return $container->services['foo*/oh-no'] = new \FooClass(); } public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_url_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_url_env.php index 6a899ee42b9e9..154080cdbf257 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_url_env.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_url_env.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Test_UrlParameters extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->parameters = $this->getDefaultParameters(); $this->services = $this->privates = []; @@ -77,8 +79,9 @@ public function getParameterBag(): ParameterBagInterface private function getDynamicParameter(string $name) { + $container = $this; $value = match ($name) { - 'hello' => $this->getEnv('url:foo'), + 'hello' => $container->getEnv('url:foo'), default => throw new ParameterNotFoundException($name), }; $this->loadedDynamicParameters[$name] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither.php index 3be9833ea75ea..5493b8bc719a4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Service_Wither extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'wither' => 'getWitherService', @@ -48,14 +50,14 @@ public function getRemovedIds(): array * * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Wither */ - protected function getWitherService() + protected static function getWitherService($container) { $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither(); $a = new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo(); $instance = $instance->withFoo1($a); - $this->services['wither'] = $instance = $instance->withFoo2($a); + $container->services['wither'] = $instance = $instance->withFoo2($a); $instance->setFoo($a); return $instance; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php index b0bbc7b640be8..e1f9a5caf605e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Service_Wither_Lazy extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'wither' => 'getWitherService', @@ -53,10 +55,12 @@ protected function createProxy($class, \Closure $factory) * * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Wither */ - protected function getWitherService($lazyLoad = true) + protected static function getWitherService($container, $lazyLoad = true) { + $containerRef = $container->ref; + if (true === $lazyLoad) { - return $this->services['wither'] = $this->createProxy('WitherProxy94fa281', fn () => \WitherProxy94fa281::createLazyProxy(fn () => $this->getWitherService(false))); + return $container->services['wither'] = $container->createProxy('WitherProxy94fa281', static fn () => \WitherProxy94fa281::createLazyProxy(static fn () => self::getWitherService($containerRef->get(), false))); } $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_staticreturntype.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_staticreturntype.php index ea71c2796a54c..61de4e02dcadf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_staticreturntype.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_staticreturntype.php @@ -15,9 +15,11 @@ class Symfony_DI_PhpDumper_Service_WitherStaticReturnType extends Container { protected $parameters = []; + protected readonly \WeakReference $ref; public function __construct() { + $this->ref = \WeakReference::create($this); $this->services = $this->privates = []; $this->methodMap = [ 'wither' => 'getWitherService', @@ -48,13 +50,13 @@ public function getRemovedIds(): array * * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\WitherStaticReturnType */ - protected function getWitherService() + protected static function getWitherService($container) { $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\WitherStaticReturnType(); $a = new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo(); - $this->services['wither'] = $instance = $instance->withFoo($a); + $container->services['wither'] = $instance = $instance->withFoo($a); $instance->setFoo($a); return $instance; diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index baaef5da89b33..307a78fa1c33e 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -37,7 +37,7 @@ "ext-psr": "<1.1|>=2", "symfony/config": "<6.1", "symfony/finder": "<5.4", - "symfony/proxy-manager-bridge": "<6.2", + "symfony/proxy-manager-bridge": "<6.3", "symfony/yaml": "<5.4" }, "provide": { From 89d0f5d3a6fd4f197cfc41dbabbfc6068b83bb7a Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Fri, 9 Dec 2022 10:00:31 +0100 Subject: [PATCH 048/475] [Notifier] Remove `ext-json` from `require` section `ext-json` is always available in PHP 8 --- src/Symfony/Component/Notifier/Bridge/Gitter/composer.json | 1 - src/Symfony/Component/Notifier/Bridge/Mercure/composer.json | 1 - src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json | 1 - src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json | 1 - src/Symfony/Component/Notifier/Bridge/Sinch/composer.json | 1 - src/Symfony/Component/Notifier/Bridge/TurboSms/composer.json | 1 - 6 files changed, 6 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Gitter/composer.json b/src/Symfony/Component/Notifier/Bridge/Gitter/composer.json index 0cf0943ec0960..8e5434f09985a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Gitter/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Gitter/composer.json @@ -17,7 +17,6 @@ ], "require": { "php": ">=8.1", - "ext-json": "*", "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json b/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json index 05ae5933c1c5c..60997223e03c8 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json @@ -17,7 +17,6 @@ ], "require": { "php": ">=8.1", - "ext-json": "*", "symfony/mercure": "^0.5.2|^0.6", "symfony/notifier": "^6.2", "symfony/service-contracts": "^1.10|^2|^3" diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json b/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json index 032c4437d035b..f66c1982e07cb 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/composer.json @@ -17,7 +17,6 @@ ], "require": { "php": ">=8.1", - "ext-json": "*", "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json b/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json index 95633479f41db..065f6ff0d49b2 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Sendinblue/composer.json @@ -17,7 +17,6 @@ ], "require": { "php": ">=8.1", - "ext-json": "*", "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json b/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json index 296eff9922507..e56fba8cc5350 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Sinch/composer.json @@ -17,7 +17,6 @@ ], "require": { "php": ">=8.1", - "ext-json": "*", "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, diff --git a/src/Symfony/Component/Notifier/Bridge/TurboSms/composer.json b/src/Symfony/Component/Notifier/Bridge/TurboSms/composer.json index edf0d509bd9ae..05210a985a3fb 100644 --- a/src/Symfony/Component/Notifier/Bridge/TurboSms/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/TurboSms/composer.json @@ -17,7 +17,6 @@ ], "require": { "php": ">=8.1", - "ext-json": "*", "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2", "symfony/polyfill-mbstring": "^1.0" From 3f312b835ae1ca181e8a5a2715cfcf5d3cc2f671 Mon Sep 17 00:00:00 2001 From: Benjamin Schoch Date: Fri, 9 Dec 2022 11:36:21 +0100 Subject: [PATCH 049/475] [Notifier] [FakeChat] Allow missing optional dependency --- .../FakeChat/FakeChatTransportFactory.php | 20 ++++++- .../Tests/FakeChatTransportFactoryTest.php | 56 +++++++++++++++++++ .../Notifier/Bridge/FakeChat/composer.json | 9 ++- 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatTransportFactory.php index ae74ee689b9af..e5bc2c2096061 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatTransportFactory.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeChat/FakeChatTransportFactory.php @@ -13,6 +13,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; use Symfony\Component\Notifier\Transport\AbstractTransportFactory; use Symfony\Component\Notifier\Transport\Dsn; @@ -23,10 +24,10 @@ */ final class FakeChatTransportFactory extends AbstractTransportFactory { - private MailerInterface $mailer; - private LoggerInterface $logger; + private ?MailerInterface $mailer; + private ?LoggerInterface $logger; - public function __construct(MailerInterface $mailer, LoggerInterface $logger) + public function __construct(MailerInterface $mailer = null, LoggerInterface $logger = null) { parent::__construct(); @@ -39,6 +40,10 @@ public function create(Dsn $dsn): FakeChatEmailTransport|FakeChatLoggerTransport $scheme = $dsn->getScheme(); if ('fakechat+email' === $scheme) { + if (null === $this->mailer) { + $this->throwMissingDependencyException($scheme, MailerInterface::class, 'symfony/mailer'); + } + $mailerTransport = $dsn->getHost(); 10000 $to = $dsn->getRequiredOption('to'); $from = $dsn->getRequiredOption('from'); @@ -47,6 +52,10 @@ public function create(Dsn $dsn): FakeChatEmailTransport|FakeChatLoggerTransport } if ('fakechat+logger' === $scheme) { + if (null === $this->logger) { + $this->throwMissingDependencyException($scheme, LoggerInterface::class, 'psr/log'); + } + return new FakeChatLoggerTransport($this->logger); } @@ -57,4 +66,9 @@ protected function getSupportedSchemes(): array { return ['fakechat+email', 'fakechat+logger']; } + + private function throwMissingDependencyException(string $scheme, string $missingDependency, string $suggestedPackage): void + { + throw new LogicException(sprintf('Cannot create a transport for scheme "%s" without providing an implementation of "%s". Try running "composer require "%s"".', $scheme, $missingDependency, $suggestedPackage)); + } } diff --git a/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatTransportFactoryTest.php index e6821ba92c769..0e595054c015c 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatTransportFactoryTest.php +++ b/src/Symfony/Component/Notifier/Bridge/FakeChat/Tests/FakeChatTransportFactoryTest.php @@ -14,10 +14,35 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; +use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Test\TransportFactoryTestCase; +use Symfony\Component\Notifier\Transport\Dsn; final class FakeChatTransportFactoryTest extends TransportFactoryTestCase { + /** + * @dataProvider missingRequiredDependencyProvider + */ + public function testMissingRequiredDependency(?MailerInterface $mailer, ?LoggerInterface $logger, string $dsn, string $message) + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage($message); + + $factory = new FakeChatTransportFactory($mailer, $logger); + $factory->create(new Dsn($dsn)); + } + + /** + * @dataProvider missingOptionalDependencyProvider + */ + public function testMissingOptionalDependency(?MailerInterface $mailer, ?LoggerInterface $logger, string $dsn) + { + $factory = new FakeChatTransportFactory($mailer, $logger); + $transport = $factory->create(new Dsn($dsn)); + + $this->assertSame($dsn, (string) $transport); + } + public function createFactory(): FakeChatTransportFactory { return new FakeChatTransportFactory($this->createMock(MailerInterface::class), $this->createMock(LoggerInterface::class)); @@ -63,4 +88,35 @@ public function unsupportedSchemeProvider(): iterable { yield ['somethingElse://default?to=recipient@email.net&from=sender@email.net']; } + + public function missingRequiredDependencyProvider(): iterable + { + $exceptionMessage = 'Cannot create a transport for scheme "%s" without providing an implementation of "%s".'; + yield 'missing mailer' => [ + null, + $this->createMock(LoggerInterface::class), + 'fakechat+email://default?to=recipient@email.net&from=sender@email.net', + sprintf($exceptionMessage, 'fakechat+email', MailerInterface::class), + ]; + yield 'missing logger' => [ + $this->createMock(MailerInterface::class), + null, + 'fakechat+logger://default', + sprintf($exceptionMessage, 'fakechat+logger', LoggerInterface::class), + ]; + } + + public function missingOptionalDependencyProvider(): iterable + { + yield 'missing logger' => [ + $this->createMock(MailerInterface::class), + null, + 'fakechat+email://default?to=recipient@email.net&from=sender@email.net', + ]; + yield 'missing mailer' => [ + null, + $this->createMock(LoggerInterface::class), + 'fakechat+logger://default', + ]; + } } diff --git a/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json b/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json index 6518b750a70fd..94b4bff1dafa9 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json @@ -23,9 +23,12 @@ "require": { "php": ">=8.1", "symfony/http-client": "^5.4|^6.0", - "symfony/notifier": "^6.2", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/mailer": "^5.4|^6.0" + "symfony/notifier": "^6.2" + }, + "require-dev": { + "symfony/mailer": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "psr/log": "^1|^2|^3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\FakeChat\\": "" }, From 02613dd455a28f1925b922ca5b050851e863be06 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 9 Dec 2022 13:49:26 +0100 Subject: [PATCH 050/475] [DI] add missing type to Container::make() --- src/Symfony/Component/DependencyInjection/Container.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 1cb5ca7403a76..7b4c8ccf88ae5 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -205,7 +205,7 @@ public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALI * * As a separate method to allow "get()" to use the really fast `??` operator. */ - private static function make($container, string $id, int $invalidBehavior) + private static function make(self $container, string $id, int $invalidBehavior) { if (isset($container->loading[$id])) { throw new ServiceCircularReferenceException($id, array_merge(array_keys($container->loading), [$id])); From 82d79b4548eba3df75a1d0c180494c2c8cad14b7 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Fri, 9 Dec 2022 14:55:05 +0100 Subject: [PATCH 051/475] =?UTF-8?q?[Console]=C2=A0Fix=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Symfony/Component/Console/Tests/ApplicationTest.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index dd964e75b597e..6ff88101f32f9 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -2132,8 +2132,6 @@ class BaseSignableCommand extends Command private $emitsSignal; private $signal; - protected static $defaultName = 'signal'; - public function __construct(bool $emitsSignal = true, int $signal = \SIGUSR1) { parent::__construct(); @@ -2173,10 +2171,9 @@ public function handleSignal(int $signal): void } } +#[AsCommand(name: 'signal')] class TerminatableCommand extends BaseSignableCommand implements SignalableCommandInterface { - protected static $defaultName = 'signal'; - public function getSubscribedSignals(): array { return SignalRegistry::isSupported() ? [\SIGINT] : []; From f7e469376594556b50b9d01dc045676e00b7e82d Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 11 Dec 2022 11:50:52 +0100 Subject: [PATCH 052/475] Update types for dd() --- src/Symfony/Component/VarDumper/Resources/functions/dump.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Symfony/Component/VarDumper/Resources/functions/dump.php b/src/Symfony/Component/VarDumper/Resources/functions/dump.php index 6221a4d182f0f..978a012fce9d0 100644 --- a/src/Symfony/Component/VarDumper/Resources/functions/dump.php +++ b/src/Symfony/Component/VarDumper/Resources/functions/dump.php @@ -32,10 +32,7 @@ function dump(mixed $var, mixed ...$moreVars): mixed } if (!function_exists('dd')) { - /** - * @return never - */ - function dd(...$vars): void + function dd(mixed ...$vars): never { if (!in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !headers_sent()) { header('HTTP/1.1 500 Internal Server Error'); From 9a248c48629e9dea12d3805b4b185216416134a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 12 Dec 2022 15:17:55 +0100 Subject: [PATCH 053/475] [Console] Fixed typo --- .../Tests/Fixtures/Style/SymfonyStyle/command/command_23.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_23.php b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_23.php index ab32dcaf3e7bf..e6228fe0ba423 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_23.php +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_23.php @@ -4,7 +4,6 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -// ensure that nested tags have no effect on the color of the '//' prefix return function (InputInterface $input, OutputInterface $output) { $output = new SymfonyStyle($input, $output); $output->text('Hello'); From 6bfc18dfa75357b04cb243aeca1d600bc1e528f2 Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 12 Dec 2022 12:36:38 +0100 Subject: [PATCH 054/475] [HttpKernel] fix wrong deprecation message --- src/Symfony/Component/HttpKernel/Kernel.php | 4 +- .../Component/HttpKernel/Tests/KernelTest.php | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 96bb3d6a9627c..3ee733f54fd67 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -660,7 +660,7 @@ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container if (isset($buildParameters['.container.dumper.inline_factories'])) { $inlineFactories = $buildParameters['.container.dumper.inline_factories']; } elseif ($container->hasParameter('container.dumper.inline_factories')) { - trigger_deprecation('symfony/http-kernel', '6.3', 'Parameter "%s" is deprecated, use ".%$1s" instead.', 'container.dumper.inline_factories'); + trigger_deprecation('symfony/http-kernel', '6.3', 'Parameter "%s" is deprecated, use ".%1$s" instead.', 'container.dumper.inline_factories'); $inlineFactories = $container->getParameter('container.dumper.inline_factories'); } @@ -668,7 +668,7 @@ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container if (isset($buildParameters['.container.dumper.inline_class_loader'])) { $inlineClassLoader = $buildParameters['.container.dumper.inline_class_loader']; } elseif ($container->hasParameter('container.dumper.inline_class_loader')) { - trigger_deprecation('symfony/http-kernel', '6.3', 'Parameter "%s" is deprecated, use ".%$1s" instead.', 'container.dumper.inline_class_loader'); + trigger_deprecation('symfony/http-kernel', '6.3', 'Parameter "%s" is deprecated, use ".%1$s" instead.', 'container.dumper.inline_class_loader'); $inlineClassLoader = $container->getParameter('container.dumper.inline_class_loader'); } diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index 690268c75fd3c..d875ccd55313d 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -12,11 +12,15 @@ namespace Symfony\Component\HttpKernel\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; +use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\Request; @@ -35,6 +39,8 @@ class KernelTest extends TestCase { + use ExpectDeprecationTrait; + protected function tearDown(): void { try { @@ -620,6 +626,45 @@ public function getContainerClass(): string $this->assertMatchesRegularExpression('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*TestDebugContainer$/', $kernel->getContainerClass()); } + /** + * @group legacy + */ + public function testKernelWithParameterDeprecation() + { + $kernel = new class('test', true) extends Kernel { + public function __construct(string $env, bool $debug) + { + $this->container = new ContainerBuilder(new ParameterBag(['container.dumper.inline_factories' => true, 'container.dumper.inline_class_loader' => true])); + parent::__construct($env, $debug); + } + + public function registerBundles(): iterable + { + return []; + } + + public function registerContainerConfiguration(LoaderInterface $loader): void + { + } + + public function boot() + { + $this->container->compile(); + parent::dumpContainer(new ConfigCache(tempnam('/tmp', 'symfony-kernel-deprecated-parameter'), true), $this->container, Container::class, $this->getContainerBaseClass()); + } + + public function getContainerClass(): string + { + return parent::getContainerClass(); + } + }; + + $this->expectDeprecation('Since symfony/http-kernel 6.3: "container.dumper.inline_factories" is deprecated, use ".container.dumper.inline_factories" instead.'); + $this->expectDeprecation('Since symfony/http-kernel 6.3: "container.dumper.inline_class_loader" is deprecated, use ".container.dumper.inline_class_loader" instead.'); + + $kernel->boot(); + } + /** * Returns a mock for the BundleInterface. */ From 7d22c79ec39a58418f32eac85b719ef8769788f3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 13 Dec 2022 10:11:19 +0100 Subject: [PATCH 055/475] fix tests --- src/Symfony/Bridge/Doctrine/composer.json | 1 + src/Symfony/Component/HttpKernel/Tests/KernelTest.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 318fc379711e1..8a852db5c10cb 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -35,6 +35,7 @@ "symfony/doctrine-messenger": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", "symfony/property-info": "^5.4|^6.0", + "symfony/proxy-manager-bridge": "^5.4|^6.0", "symfony/security-core": "^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/uid": "^5.4|^6.0", diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index d875ccd55313d..036b5992802e8 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -659,8 +659,8 @@ public function getContainerClass(): string } }; - $this->expectDeprecation('Since symfony/http-kernel 6.3: "container.dumper.inline_factories" is deprecated, use ".container.dumper.inline_factories" instead.'); - $this->expectDeprecation('Since symfony/http-kernel 6.3: "container.dumper.inline_class_loader" is deprecated, use ".container.dumper.inline_class_loader" instead.'); + $this->expectDeprecation('Since symfony/http-kernel 6.3: Parameter "container.dumper.inline_factories" is deprecated, use ".container.dumper.inline_factories" instead.'); + $this->expectDeprecation('Since symfony/http-kernel 6.3: Parameter "container.dumper.inline_class_loader" is deprecated, use ".container.dumper.inline_class_loader" instead.'); $kernel->boot(); } From cd02eacdbb9884b1eef3df9ec986e445fc3cbe10 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 21 Nov 2022 19:36:57 +0100 Subject: [PATCH 056/475] [FrameworkBundle][DX] Allow to avoid `limit` definition in a RateLimiter configuration when using the `no_limit` policy --- src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../FrameworkBundle/DependencyInjection/Configuration.php | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index f6cfae42e6ac1..1ecd16712ab2f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `DomCrawlerAssertionsTrait::assertSelectorCount(int $count, string $selector)` + * Allow to avoid `limit` definition in a RateLimiter configuration when using the `no_limit` policy 6.2 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 7b063b614d4ba..d3d01bec9ec7f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -2104,7 +2104,6 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $ ->end() ->integerNode('limit') ->info('The maximum allowed hits in a fixed interval or burst') - ->isRequired() ->end() ->scalarNode('interval') ->info('Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).') @@ -2119,6 +2118,10 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $ ->end() ->end() ->end() + ->validate() + ->ifTrue(function ($v) { return 'no_limit' !== $v['policy'] && !isset($v['limit']); }) + ->thenInvalid('A limit must be provided when using a policy different than "no_limit".') + ->end() ->end() ->end() ->end() From eed0ae4ffe6dce426c2756570d6f0b7f008084e7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 14 Dec 2022 18:04:21 +0100 Subject: [PATCH 057/475] Fix tests --- .../Fixtures/php/notifier_with_disabled_message_bus.php | 1 + .../Fixtures/php/notifier_with_specific_message_bus.php | 1 + .../Fixtures/xml/notifier_with_disabled_message_bus.xml | 2 +- .../Fixtures/xml/notifier_with_specific_message_bus.xml | 2 +- .../Fixtures/yml/notifier_with_disabled_message_bus.yml | 1 + .../Fixtures/yml/notifier_with_specific_message_bus.yml | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php index 014b54b94a5dd..88bc913c9d9b6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php @@ -1,6 +1,7 @@ loadFromExtension('framework', [ + 'http_method_override' => false, 'messenger' => [ 'enabled' => true, ], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php index 75074e073ce29..2413edc1aeab1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php @@ -1,6 +1,7 @@ loadFromExtension('framework', [ + 'http_method_override' => false, 'messenger' => [ 'enabled' => true, ], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml index 599bd23cb8f43..3b8d00b146519 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml @@ -6,7 +6,7 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml index 62373497056ac..abe55d16ad50c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml @@ -6,7 +6,7 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml index 08b3d6ad6e759..7790875c66f93 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml @@ -1,4 +1,5 @@ framework: + http_method_override: false messenger: enabled: true mailer: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml index 1851717bd9627..adf4133857b06 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml @@ -1,4 +1,5 @@ framework: + http_method_override: false messenger: enabled: true mailer: From 2519c5c8303782be71649bbd72408d32ed0a9b21 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 1 Dec 2022 18:32:26 +0100 Subject: [PATCH 058/475] [VarDumper] Add support of named arguments to `dd()` and `dump()` to display the argument name --- src/Symfony/Component/VarDumper/CHANGELOG.md | 1 + .../Component/VarDumper/Caster/ScalarStub.php | 27 ++++++++++++ .../Component/VarDumper/Caster/StubCaster.php | 8 ++++ .../VarDumper/Cloner/AbstractCloner.php | 1 + .../Component/VarDumper/Cloner/Data.php | 12 +++++- .../Component/VarDumper/Cloner/Stub.php | 1 + .../VarDumper/Dumper/ContextualizedDumper.php | 2 +- .../VarDumper/Resources/functions/dump.php | 33 ++++++++++---- .../VarDumper/Tests/Caster/StubCasterTest.php | 16 ++++++- .../VarDumper/Tests/Dumper/FunctionsTest.php | 43 ++++++++++++++++++- src/Symfony/Component/VarDumper/VarDumper.php | 18 ++++++-- 11 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 src/Symfony/Component/VarDumper/Caster/ScalarStub.php diff --git a/src/Symfony/Component/VarDumper/CHANGELOG.md b/src/Symfony/Component/VarDumper/CHANGELOG.md index 97204fc67d042..c5abeee346a06 100644 --- a/src/Symfony/Component/VarDumper/CHANGELOG.md +++ b/src/Symfony/Component/VarDumper/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add caster for `WeakMap` + * Add support of named arguments to `dd()` and `dump()` to display the argument name 6.2 --- diff --git a/src/Symfony/Component/VarDumper/Caster/ScalarStub.php b/src/Symfony/Component/VarDumper/Caster/ScalarStub.php new file mode 100644 index 0000000000000..3bb1935b88195 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/ScalarStub.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents any arbitrary value. + * + * @author Alexandre Daubois + */ +class ScalarStub extends Stub +{ + public function __construct(mixed $value) + { + $this->value = $value; + } +} diff --git a/src/Symfony/Component/VarDumper/Caster/StubCaster.php b/src/Symfony/Component/VarDumper/Caster/StubCaster.php index 32ead7c277722..9318ad1f59e2f 100644 --- a/src/Symfony/Component/VarDumper/Caster/StubCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/StubCaster.php @@ -81,4 +81,12 @@ public static function castEnum(EnumStub $c, array $a, Stub $stub, bool $isNeste return $a; } + + public static function castScalar(ScalarStub $scalarStub, array $a, Stub $stub) + { + $stub->type = Stub::TYPE_SCALAR; + $stub->attr['value'] = $scalarStub->value; + + return $a; + } } diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index b72202587148c..bcd3013716e9e 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -28,6 +28,7 @@ abstract class AbstractCloner implements ClonerInterface 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], + 'Symfony\Component\VarDumper\Caster\ScalarStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castScalar'], 'Fiber' => ['Symfony\Component\VarDumper\Caster\FiberCaster', 'castFiber'], diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php index 6ecb883e83ad8..d87d56906e6eb 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -211,6 +211,11 @@ public function withContext(array $context): static return $data; } + public function getContext(): array + { + return $this->context; + } + /** * Seeks to a specific key in nested data structures. */ @@ -262,11 +267,12 @@ public function dump(DumperInterface $dumper) { $refs = [0]; $cursor = new Cursor(); + $label = $this->context['label'] ?? ''; if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) { $cursor->attr['if_links'] = true; $cursor->hashType = -1; - $dumper->dumpScalar($cursor, 'default', '^'); + $dumper->dumpScalar($cursor, 'default', $label.'^'); $cursor->attr = ['if_links' => true]; $dumper->dumpScalar($cursor, 'default', ' '); $cursor->hashType = 0; @@ -362,6 +368,10 @@ private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); break; + case Stub::TYPE_SCALAR: + $dumper->dumpScalar($cursor, 'default', $item->attr['value']); + break; + default: throw new \RuntimeException(sprintf('Unexpected Stub type: "%s".', $item->type)); } diff --git a/src/Symfony/Component/VarDumper/Cloner/Stub.php b/src/Symfony/Component/VarDumper/Cloner/Stub.php index 1c5b887120f53..0c2a4b9d0a5cf 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Stub.php +++ b/src/Symfony/Component/VarDumper/Cloner/Stub.php @@ -23,6 +23,7 @@ class Stub public const TYPE_ARRAY = 3; public const TYPE_OBJECT = 4; public const TYPE_RESOURCE = 5; + public const TYPE_SCALAR = 6; public const STRING_BINARY = 1; public const STRING_UTF8 = 2; diff --git a/src/Symfony/Component/VarDumper/Dumper/ContextualizedDumper.php b/src/Symfony/Component/VarDumper/Dumper/ContextualizedDumper.php index 1ba803d813894..c10cd4442f6f6 100644 --- a/src/Symfony/Component/VarDumper/Dumper/ContextualizedDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/ContextualizedDumper.php @@ -33,7 +33,7 @@ public function __construct(DataDumperInterface $wrappedDumper, array $contextPr public function dump(Data $data) { - $context = []; + $context = $data->getContext(); foreach ($this->contextProviders as $contextProvider) { $context[$contextProvider::class] = $contextProvider->getContext(); } diff --git a/src/Symfony/Component/VarDumper/Resources/functions/dump.php b/src/Symfony/Component/VarDumper/Resources/functions/dump.php index 6221a4d182f0f..4e7652c4f7be8 100644 --- a/src/Symfony/Component/VarDumper/Resources/functions/dump.php +++ b/src/Symfony/Component/VarDumper/Resources/functions/dump.php @@ -9,25 +9,36 @@ * file that was distributed with this source code. */ +use Symfony\Component\VarDumper\Caster\ScalarStub; use Symfony\Component\VarDumper\VarDumper; if (!function_exists('dump')) { /** * @author Nicolas Grekas + * @author Alexandre Daubois */ - function dump(mixed $var, mixed ...$moreVars): mixed + function dump(mixed ...$vars): mixed { - VarDumper::dump($var); + if (!$vars) { + VarDumper::dump(new ScalarStub('🐛')); - foreach ($moreVars as $v) { - VarDumper::dump($v); + return null; } - if (1 < func_num_args()) { - return func_get_args(); + if (isset($vars[0]) && 1 === count($vars)) { + VarDumper::dump($vars[0]); + $k = 0; + } else { + foreach ($vars as $k => $v) { + VarDumper::dump($v, is_int($k) ? 1 + $k : $k); + } } - return $var; + if (1 < count($vars)) { + return $vars; + } + + return $vars[$k]; } } @@ -41,8 +52,12 @@ function dd(...$vars): void header('HTTP/1.1 500 Internal Server Error'); } - foreach ($vars as $v) { - VarDumper::dump($v); + if (isset($vars[0]) && 1 === count($vars)) { + VarDumper::dump($vars[0]); + } else { + foreach ($vars as $k => $v) { + VarDumper::dump($v, is_int($k) ? 1 + $k : $k); + } } exit(1); diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php index 6ca2dad3a48d3..8b3e12b5c1d72 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php @@ -15,6 +15,7 @@ use Symfony\Component\VarDumper\Caster\ArgsStub; use Symfony\Component\VarDumper\Caster\ClassStub; use Symfony\Component\VarDumper\Caster\LinkStub; +use Symfony\Component\VarDumper\Caster\ScalarStub; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Symfony\Component\VarDumper\Test\VarDumperTestTrait; @@ -87,6 +88,19 @@ public function testArgsStubWithClosure() $this->assertDumpMatchesFormat($expectedDump, $args); } + public function testEmptyStub() + { + $args = [new ScalarStub('🐛')]; + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => 🐛 +] +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $args); + } + public function testLinkStub() { $var = [new LinkStub(__CLASS__, 0, __FILE__)]; @@ -203,7 +217,7 @@ public function testClassStubWithAnonymousClass() $expectedDump = <<<'EODUMP' array:1 [ - 0 => "Exception@anonymous" + 0 => "Exception@anonymous" ] EODUMP; diff --git a/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php b/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php index 7444d4bf58425..d158d7eb930aa 100644 --- a/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php @@ -18,6 +18,17 @@ class FunctionsTest extends TestCase { + public function testDumpWithoutArg() + { + $this->setupVarDumper(); + + ob_start(); + $return = dump(); + ob_end_clean(); + + $this->assertNull($return); + } + public function testDumpReturnsFirstArg() { $this->setupVarDumper(); @@ -28,7 +39,20 @@ public function testDumpReturnsFirstArg() $return = dump($var1); ob_end_clean(); - $this->assertEquals($var1, $return); + $this->assertSame($var1, $return); + } + + public function testDumpReturnsFirstNamedArgWithoutSectionName() + { + $this->setupVarDumper(); + + $var1 = 'a'; + + ob_start(); + $return = dump(first: $var1); + ob_end_clean(); + + $this->assertSame($var1, $return); } public function testDumpReturnsAllArgsInArray() @@ -43,7 +67,22 @@ public function testDumpReturnsAllArgsInArray() $return = dump($var1, $var2, $var3); ob_end_clean(); - $this->assertEquals([$var1, $var2, $var3], $return); + $this->assertSame([$var1, $var2, $var3], $return); + } + + public function testDumpReturnsAllNamedArgsInArray() + { + $this->setupVarDumper(); + + $var1 = 'a'; + $var2 = 'b'; + $var3 = 'c'; + + ob_start(); + $return = dump($var1, second: $var2, third: $var3); + ob_end_clean(); + + $this->assertSame([$var1, 'second' => $var2, 'third' => $var3], $return); } protected function setupVarDumper() diff --git a/src/Symfony/Component/VarDumper/VarDumper.php b/src/Symfony/Component/VarDumper/VarDumper.php index 840bfd6496817..7c160166a9788 100644 --- a/src/Symfony/Component/VarDumper/VarDumper.php +++ b/src/Symfony/Component/VarDumper/VarDumper.php @@ -37,13 +37,17 @@ class VarDumper */ private static $handler; - public static function dump(mixed $var) + /** + * @param string|null $label + */ + public static function dump(mixed $var/* , string $label = null */) { + $label = 2 <= \func_num_args() ? func_get_arg(1) : null; if (null === self::$handler) { self::register(); } - return (self::$handler)($var); + return (self::$handler)($var, $label); } public static function setHandler(callable $callable = null): ?callable @@ -90,8 +94,14 @@ private static function register(): void $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); } - self::$handler = function ($var) use ($cloner, $dumper) { - $dumper->dump($cloner->cloneVar($var)); + self::$handler = function ($var, string $label = null) use ($cloner, $dumper) { + $var = $cloner->cloneVar($var); + + if (null !== $label) { + $var = $var->withContext(['label' => $label]); + } + + $dumper->dump($var); }; } From e41090927405bb35b03fddfe1cf7ffc9fcae2520 Mon Sep 17 00:00:00 2001 From: Maxim Dovydenok Date: Sat, 20 Aug 2022 16:41:06 +0300 Subject: [PATCH 059/475] [Notifier] Allow to update Slack messages Update existing SlackTransport to allow message updates. Because chat.update API method only allows channel ids, this PR also includes updates to SentMessage --- .../Notifier/Bridge/Slack/CHANGELOG.md | 5 +++ .../Notifier/Bridge/Slack/SlackOptions.php | 2 +- .../Bridge/Slack/SlackSentMessage.php | 41 +++++++++++++++++ .../Notifier/Bridge/Slack/SlackTransport.php | 12 +++-- .../Bridge/Slack/Tests/SlackTransportTest.php | 44 +++++++++++++++++-- .../Slack/UpdateMessageSlackOptions.php | 26 +++++++++++ 6 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 src/Symfony/Component/Notifier/Bridge/Slack/SlackSentMessage.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Slack/UpdateMessageSlackOptions.php diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md index 77b83cf9ee14d..05fecc49d9c2c 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/Slack/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Allow to update Slack messages + 6.0 --- diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php index fdded36c9b923..f6fe5d5411e18 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackOptions.php @@ -21,7 +21,7 @@ /** * @author Fabien Potencier */ -final class SlackOptions implements MessageOptionsInterface +class SlackOptions implements MessageOptionsInterface { private const MAX_BLOCKS = 50; diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackSentMessage.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackSentMessage.php new file mode 100644 index 0000000000000..558eb5b57f8b9 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackSentMessage.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Slack; + +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; + +/** + * @author Maxim Dovydenok + */ +final class SlackSentMessage extends SentMessage +{ + private string $channelId; + + public function __construct(MessageInterface $original, string $transport, string $channelId, string $messageId) + { + parent::__construct($original, $transport); + $this->channelId = $channelId; + $this->setMessageId($messageId); + } + + public function getChannelId(): string + { + return $this->channelId; + } + + public function getUpdateMessage(string $subject, array $options = []): ChatMessage + { + return new ChatMessage($subject, new UpdateMessageSlackOptions($this->channelId, $this->getMessageId(), $options)); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php index f253e90ebb9be..1f30d8a0fd74f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/SlackTransport.php @@ -17,7 +17,6 @@ use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; -use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Transport\AbstractTransport; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; @@ -63,7 +62,7 @@ public function supports(MessageInterface $message): bool /** * @see https://api.slack.com/methods/chat.postMessage */ - protected function doSend(MessageInterface $message): SentMessage + protected function doSend(MessageInterface $message): SlackSentMessage { if (!$message instanceof ChatMessage) { throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); @@ -82,7 +81,9 @@ protected function doSend(MessageInterface $message): SentMessage $options['channel'] = $message->getRecipientId() ?: $this->chatChannel; } $options['text'] = $message->getSubject(); - $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/chat.postMessage', [ + + $apiMethod = $opts instanceof UpdateMessageSlackOptions ? 'chat.update' : 'chat.postMessage'; + $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/'.$apiMethod, [ 'json' => array_filter($options), 'auth_bearer' => $this->accessToken, 'headers' => [ @@ -107,9 +108,6 @@ protected function doSend(MessageInterface $message): SentMessage throw new TransportException(sprintf('Unable to post the Slack message: "%s"%s.', $result['error'], $errors), $response); } - $sentMessage = new SentMessage($message, (string) $this); - $sentMessage->setMessageId($result['ts']); - - return $sentMessage; + return new SlackSentMessage($message, (string) $this, $result['channel'], $result['ts']); } } diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php index 38a6931127797..56a7e93a9a9b5 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/Slack/Tests/SlackTransportTest.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Notifier\Bridge\Slack\SlackOptions; +use Symfony\Component\Notifier\Bridge\Slack\SlackSentMessage; use Symfony\Component\Notifier\Bridge\Slack\SlackTransport; use Symfony\Component\Notifier\Exception\InvalidArgumentException; use Symfony\Component\Notifier\Exception\LogicException; @@ -115,7 +116,7 @@ public function testSendWithOptions() $response->expects($this->once()) ->method('getContent') - ->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247'])); + ->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247', 'channel' => 'C123456'])); $expectedBody = json_encode(['channel' => $channel, 'text' => $message]); @@ -130,6 +131,8 @@ public function testSendWithOptions() $sentMessage = $transport->send(new ChatMessage('testMessage')); $this->assertSame('1503435956.000247', $sentMessage->getMessageId()); + $this->assertInstanceOf(SlackSentMessage::class, $sentMessage); + $this->assertSame('C123456', $sentMessage->getChannelId()); } public function testSendWithNotification() @@ -145,7 +148,7 @@ public function testSendWithNotification() $response->expects($this->once()) ->method('getContent') - ->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247'])); + ->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247', 'channel' => 'C123456'])); $notification = new Notification($message); $chatMessage = ChatMessage::fromNotification($notification); @@ -223,7 +226,7 @@ public function testSendIncludesContentTypeWithCharset() $response->expects($this->once()) ->method('getContent') - ->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247'])); + ->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247', 'channel' => 'C123456'])); $client = new MockHttpClient(function (string $method, string $url, array $options = []) use ($response): ResponseInterface { $this->assertContains('Content-Type: application/json; charset=utf-8', $options['headers']); @@ -263,4 +266,39 @@ public function testSendWithErrorsIncluded() $transport->send(new ChatMessage('testMessage')); } + + public function testUpdateMessage() + { + $response = $this->createMock(ResponseInterface::class); + + $response->expects($this->exactly(2)) + ->method('getStatusCode') + ->willReturn(200); + + $response->expects($this->once()) + ->method('getContent') + ->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247', 'channel' => 'C123456'])); + + $sentMessage = new SlackSentMessage(new ChatMessage('Hello'), 'slack', 'C123456', '1503435956.000247'); + $chatMessage = $sentMessage->getUpdateMessage('Hello World'); + + $expectedBody = json_encode([ + 'channel' => 'C123456', + 'ts' => '1503435956.000247', + 'text' => 'Hello World', + ]); + + $client = new MockHttpClient(function (string $method, string $url, array $options = []) use ($response, $expectedBody): ResponseInterface { + $this->assertJsonStringEqualsJsonString($expectedBody, $options['body']); + $this->assertStringEndsWith('chat.update', $url); + + return $response; + }); + + $transport = $this->createTransport($client, 'another-channel'); + + $sentMessage = $transport->send($chatMessage); + + $this->assertSame('1503435956.000247', $sentMessage->getMessageId()); + } } diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/UpdateMessageSlackOptions.php b/src/Symfony/Component/Notifier/Bridge/Slack/UpdateMessageSlackOptions.php new file mode 100644 index 0000000000000..886ac09e3c4c5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Slack/UpdateMessageSlackOptions.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Slack; + +/** + * @author Maxim Dovydenok + */ +final class UpdateMessageSlackOptions extends SlackOptions +{ + public function __construct(string $channelId, string $messageId, array $options = []) + { + $options['channel'] = $channelId; + $options['ts'] = $messageId; + + parent::__construct($options); + } +} From aebdc581fa2351319af4d13f57dbdfff924270f7 Mon Sep 17 00:00:00 2001 From: Allison Guilhem Date: Mon, 31 Oct 2022 12:45:42 +0100 Subject: [PATCH 060/475] [HttpFoundation] Create migration for session table when pdo handler is used --- .../AbstractSchemaSubscriber.php | 56 +++++++++++++++++++ ...ctrineDbalCacheAdapterSchemaSubscriber.php | 20 ++----- ...engerTransportDoctrineSchemaSubscriber.php | 15 ++--- .../PdoSessionHandlerSchemaSubscriber.php | 37 ++++++++++++ ...eTokenProviderDoctrineSchemaSubscriber.php | 19 +------ .../RememberMe/DoctrineTokenProvider.php | 11 ++-- .../PdoSessionHandlerSchemaSubscriberTest.php | 42 ++++++++++++++ src/Symfony/Bridge/Doctrine/composer.json | 1 + .../Cache/Adapter/DoctrineDbalAdapter.php | 12 ++-- .../Component/HttpFoundation/CHANGELOG.md | 5 ++ .../Handler/MigratingSessionHandler.php | 11 +--- .../Storage/Handler/PdoSessionHandler.php | 53 +++++++++++++++++- .../Storage/Handler/PdoSessionHandlerTest.php | 31 ++++++++++ .../Component/HttpFoundation/composer.json | 1 + .../Tests/Transport/ConnectionTest.php | 6 +- .../Bridge/Doctrine/Transport/Connection.php | 7 +-- .../Doctrine/Transport/DoctrineTransport.php | 8 ++- 17 files changed, 265 insertions(+), 70 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaSubscriber.php create mode 100644 src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaSubscriber.php create mode 100644 src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaSubscriber.php new file mode 100644 index 0000000000000..52b81d6b98c58 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaSubscriber.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\Common\EventSubscriber; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Doctrine\ORM\Tools\ToolEvents; + +abstract class AbstractSchemaSubscriber implements EventSubscriber +{ + abstract public function postGenerateSchema(GenerateSchemaEventArgs $event): void; + + public function getSubscribedEvents(): array + { + if (!class_exists(ToolEvents::class)) { + return []; + } + + return [ + ToolEvents::postGenerateSchema, + ]; + } + + protected function getIsSameDatabaseChecker(Connection $connection): \Closure + { + return static function (\Closure $exec) use ($connection): bool { + $checkTable = 'schema_subscriber_check_'.bin2hex(random_bytes(7)); + $connection->executeStatement(sprintf('CREATE TABLE %s (id INTEGER NOT NULL)', $checkTable)); + + try { + $exec(sprintf('DROP TABLE %s', $checkTable)); + } catch (\Exception) { + // ignore + } + + try { + $connection->executeStatement(sprintf('DROP TABLE %s', $checkTable)); + + return false; + } catch (TableNotFoundException) { + return true; + } + }; + } +} diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriber.php index bf9b793175f3f..5df11249f92df 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriber.php @@ -11,9 +11,7 @@ namespace Symfony\Bridge\Doctrine\SchemaListener; -use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; -use Doctrine\ORM\Tools\ToolEvents; use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; /** @@ -22,7 +20,7 @@ * * @author Ryan Weaver */ -final class DoctrineDbalCacheAdapterSchemaSubscriber implements EventSubscriber +final class DoctrineDbalCacheAdapterSchemaSubscriber extends AbstractSchemaSubscriber { private $dbalAdapters; @@ -36,20 +34,10 @@ public function __construct(iterable $dbalAdapters) public function postGenerateSchema(GenerateSchemaEventArgs $event): void { - $dbalConnection = $event->getEntityManager()->getConnection(); - foreach ($this->dbalAdapters as $dbalAdapter) { - $dbalAdapter->configureSchema($event->getSchema(), $dbalConnection); - } - } + $connection = $event->getEntityManager()->getConnection(); - public function getSubscribedEvents(): array - { - if (!class_exists(ToolEvents::class)) { - return []; + foreach ($this->dbalAdapters as $dbalAdapter) { + $dbalAdapter->configureSchema($event->getSchema(), $connection, $this->getIsSameDatabaseChecker($connection)); } - - return [ - ToolEvents::postGenerateSchema, - ]; } } diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php index 3cf100615a51c..155c5e9602515 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php @@ -11,11 +11,9 @@ namespace Symfony\Bridge\Doctrine\SchemaListener; -use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; use Doctrine\DBAL\Events; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; -use Doctrine\ORM\Tools\ToolEvents; use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport; use Symfony\Component\Messenger\Transport\TransportInterface; @@ -24,7 +22,7 @@ * * @author Ryan Weaver */ -final class MessengerTransportDoctrineSchemaSubscriber implements EventSubscriber +final class MessengerTransportDoctrineSchemaSubscriber extends AbstractSchemaSubscriber { private const PROCESSING_TABLE_FLAG = self::class.':processing'; @@ -40,13 +38,14 @@ public function __construct(iterable $transports) public function postGenerateSchema(GenerateSchemaEventArgs $event): void { - $dbalConnection = $event->getEntityManager()->getConnection(); + $connection = $event->getEntityManager()->getConnection(); + foreach ($this->transports as $transport) { if (!$transport instanceof DoctrineTransport) { continue; } - $transport->configureSchema($event->getSchema(), $dbalConnection); + $transport->configureSchema($event->getSchema(), $connection, $this->getIsSameDatabaseChecker($connection)); } } @@ -89,11 +88,7 @@ public function onSchemaCreateTable(SchemaCreateTableEventArgs $event): void public function getSubscribedEvents(): array { - $subscribedEvents = []; - - if (class_exists(ToolEvents::class)) { - $subscribedEvents[] = ToolEvents::postGenerateSchema; - } + $subscribedEvents = parent::getSubscribedEvents(); if (class_exists(Events::class)) { $subscribedEvents[] = Events::onSchemaCreateTable; diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaSubscriber.php new file mode 100644 index 0000000000000..a14a800cbb260 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaSubscriber.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + +final class PdoSessionHandlerSchemaSubscriber extends AbstractSchemaSubscriber +{ + private iterable $pdoSessionHandlers; + + /** + * @param iterable $pdoSessionHandlers + */ + public function __construct(iterable $pdoSessionHandlers) + { + $this->pdoSessionHandlers = $pdoSessionHandlers; + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + $connection = $event->getEntityManager()->getConnection(); + + foreach ($this->pdoSessionHandlers as $pdoSessionHandler) { + $pdoSessionHandler->configureSchema($event->getSchema(), $this->getIsSameDatabaseChecker($connection)); + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaSubscriber.php index 2eba94ff23c06..bd7540f1bbe44 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaSubscriber.php @@ -11,9 +11,7 @@ namespace Symfony\Bridge\Doctrine\SchemaListener; -use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; -use Doctrine\ORM\Tools\ToolEvents; use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider; use Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler; use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; @@ -23,7 +21,7 @@ * * @author Wouter de Jong */ -final class RememberMeTokenProviderDoctrineSchemaSubscriber implements EventSubscriber +final class RememberMeTokenProviderDoctrineSchemaSubscriber extends AbstractSchemaSubscriber { private iterable $rememberMeHandlers; @@ -37,26 +35,15 @@ public function __construct(iterable $rememberMeHandlers) public function postGenerateSchema(GenerateSchemaEventArgs $event): void { - $dbalConnection = $event->getEntityManager()->getConnection(); + $connection = $event->getEntityManager()->getConnection(); foreach ($this->rememberMeHandlers as $rememberMeHandler) { if ( $rememberMeHandler instanceof PersistentRememberMeHandler && ($tokenProvider = $rememberMeHandler->getTokenProvider()) instanceof DoctrineTokenProvider ) { - $tokenProvider->configureSchema($event->getSchema(), $dbalConnection); + $tokenProvider->configureSchema($event->getSchema(), $connection, $this->getIsSameDatabaseChecker($connection)); } } } - - public function getSubscribedEvents(): array - { - if (!class_exists(ToolEvents::class)) { - return []; - } - - return [ - ToolEvents::postGenerateSchema, - ]; - } } diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index 60f883a0e465f..bc2e7d915a7aa 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -188,15 +188,18 @@ public function updateExistingToken(PersistentTokenInterface $token, #[\Sensitiv /** * Adds the Table to the Schema if "remember me" uses this Connection. + * + * @param \Closure $isSameDatabase */ - public function configureSchema(Schema $schema, Connection $forConnection): void + public function configureSchema(Schema $schema, Connection $forConnection/* , \Closure $isSameDatabase */): void { - // only update the schema for this connection - if ($forConnection !== $this->conn) { + if ($schema->hasTable('rememberme_token')) { return; } - if ($schema->hasTable('rememberme_token')) { + $isSameDatabase = 2 < \func_num_args() ? func_get_arg(2) : static fn () => false; + + if ($forConnection !== $this->conn && !$isSameDatabase($this->conn->executeStatement(...))) { return; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php new file mode 100644 index 0000000000000..092d8be07e892 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\SchemaListener; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\SchemaListener\PdoSessionHandlerSchemaSubscriber; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + +class PdoSessionHandlerSchemaSubscriberTest extends TestCase +{ + public function testPostGenerateSchemaPdo() + { + $schema = new Schema(); + $dbalConnection = $this->createMock(Connection::class); + $entityManager = $this->createMock(EntityManagerInterface::class); + $entityManager->expects($this->once()) + ->method('getConnection') + ->willReturn($dbalConnection); + $event = new GenerateSchemaEventArgs($entityManager, $schema); + + $pdoSessionHandler = $this->createMock(PdoSessionHandler::class); + $pdoSessionHandler->expects($this->once()) + ->method('configureSchema') + ->with($schema, fn() => true); + + $subscriber = new PdoSessionHandlerSchemaSubscriber([$pdoSessionHandler]); + $subscriber->postGenerateSchema($event); + } +} diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 318fc379711e1..4925762f5f5f1 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -56,6 +56,7 @@ "symfony/cache": "<5.4", "symfony/dependency-injection": "<6.2", "symfony/form": "<5.4", + "symfony/http-foundation": "<6.3", "symfony/http-kernel": "<6.2", "symfony/messenger": "<5.4", "symfony/property-info": "<5.4", diff --git a/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php b/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter D7AE .php index 806eba40da8ca..2615e29429f50 100644 --- a/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php @@ -98,14 +98,18 @@ public function createTable(): void } } - public function configureSchema(Schema $schema, Connection $forConnection): void + /** + * @param \Closure $isSameDatabase + */ + public function configureSchema(Schema $schema, Connection $forConnection/* , \Closure $isSameDatabase */): void { - // only update the schema for this connection - if ($forConnection !== $this->conn) { + if ($schema->hasTable($this->table)) { return; } - if ($schema->hasTable($this->table)) { + $isSameDatabase = 2 < \func_num_args() ? func_get_arg(2) : static fn () => false; + + if ($forConnection !== $this->conn && !$isSameDatabase($this->conn->executeStatement(...))) { return; } diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index fdea3d67e1b19..ae0902547f8f0 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Create migration for session table when pdo handler is used + 6.2 --- diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php index 1d425523681a1..46fbeb006f453 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php @@ -22,15 +22,8 @@ */ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { - /** - * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface - */ - private \SessionHandlerInterface $currentHandler; - - /** - * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface - */ - private \SessionHandlerInterface $writeOnlyHandler; + private \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface $currentHandler; + private \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface $writeOnlyHandler; public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 302372627ddb5..a046aef95b08c 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -11,6 +11,9 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Types\Types; + /** * Session handler using a PDO connection to read and write data. * @@ -175,6 +178,52 @@ public function __construct(\PDO|string $pdoOrDsn = null, array $options = []) $this->ttl = $options['ttl'] ?? null; } + public function configureSchema(Schema $schema, \Closure $isSameDatabase): void + { + if ($schema->hasTable($this->table) || !$isSameDatabase($this->getConnection()->exec(...))) { + return; + } + + $table = $schema->createTable($this->table); + switch ($this->driver) { + case 'mysql': + $table->addColumn($this->idCol, Types::BINARY)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addOption('collate', 'utf8mb4_bin'); + $table->addOption('engine', 'InnoDB'); + break; + case 'sqlite': + $table->addColumn($this->idCol, Types::TEXT)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'pgsql': + $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BINARY)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'oci': + $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'sqlsrv': + $table->addColumn($this->idCol, Types::TEXT)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + break; + default: + throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); + } + $table->setPrimaryKey([$this->idCol]); + } + /** * Creates the table to store sessions which can be called once for setup. * @@ -441,8 +490,8 @@ private function buildDsnFromUrl(string $dsnOrUrl): string return $dsn; } } - // If "unix_socket" is not in the query, we continue with the same process as pgsql - // no break + // If "unix_socket" is not in the query, we continue with the same process as pgsql + // no break case 'pgsql': $dsn ??= 'pgsql:'; diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php index 34dad9685d5f6..07a82d09a57a5 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -11,11 +11,13 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use Doctrine\DBAL\Schema\Schema; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; /** * @requires extension pdo_sqlite + * * @group time-sensitive */ class PdoSessionHandlerTest extends TestCase @@ -326,6 +328,35 @@ public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPa } } + public function testConfigureSchemaDifferentDatabase() + { + $schema = new Schema(); + + $pdoSessionHandler = new PdoSessionHandler($this->getMemorySqlitePdo()); + $pdoSessionHandler->configureSchema($schema, fn() => false); + $this->assertFalse($schema->hasTable('sessions')); + } + + public function testConfigureSchemaSameDatabase() + { + $schema = new Schema(); + + $pdoSessionHandler = new PdoSessionHandler($this->getMemorySqlitePdo()); + $pdoSessionHandler->configureSchema($schema, fn() => true); + $this->assertTrue($schema->hasTable('sessions')); + } + + public function testConfigureSchemaTableExistsPdo() + { + $schema = new Schema(); + $schema->createTable('sessions'); + + $pdoSessionHandler = new PdoSessionHandler($this->getMemorySqlitePdo()); + $pdoSessionHandler->configureSchema($schema, fn() => true); + $table = $schema->getTable('sessions'); + $this->assertEmpty($table->getColumns(), 'The table was not overwritten'); + } + public function provideUrlDsnPairs() { yield ['mysql://localhost/test', 'mysql:host=localhost;dbname=test;']; diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json index ddcb502515b74..2023300b8cc2d 100644 --- a/src/Symfony/Component/HttpFoundation/composer.json +++ b/src/Symfony/Component/HttpFoundation/composer.json @@ -22,6 +22,7 @@ "symfony/polyfill-php83": "^1.27" }, "require-dev": { + "doctrine/dbal": "^2.13.1|^3.0", "predis/predis": "~1.0", "symfony/cache": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php index 7e576a5580dff..5795237bf207c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php @@ -411,7 +411,7 @@ public function testConfigureSchema() $schema = new Schema(); $connection = new Connection(['table_name' => 'queue_table'], $driverConnection); - $connection->configureSchema($schema, $driverConnection); + $connection->configureSchema($schema, $driverConnection, fn() => true); $this->assertTrue($schema->hasTable('queue_table')); } @@ -422,7 +422,7 @@ public function testConfigureSchemaDifferentDbalConnection() $schema = new Schema(); $connection = new Connection([], $driverConnection); - $connection->configureSchema($schema, $driverConnection2); + $connection->configureSchema($schema, $driverConnection2, fn() => true); $this->assertFalse($schema->hasTable('messenger_messages')); } @@ -433,7 +433,7 @@ public function testConfigureSchemaTableExists() $schema->createTable('messenger_messages'); $connection = new Connection([], $driverConnection); - $connection->configureSchema($schema, $driverConnection); + $connection->configureSchema($schema, $driverConnection, fn() => true); $table = $schema->getTable('messenger_messages'); $this->assertEmpty($table->getColumns(), 'The table was not overwritten'); } diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index 45113221fbb03..2c4e6a4746ae5 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -324,14 +324,13 @@ public function find(mixed $id): ?array /** * @internal */ - public function configureSchema(Schema $schema, DBALConnection $forConnection): void + public function configureSchema(Schema $schema, DBALConnection $forConnection, \Closure $isSameDatabase): void { - // only update the schema for this connection - if ($forConnection !== $this->driverConnection) { + if ($schema->hasTable($this->configuration['table_name'])) { return; } - if ($schema->hasTable($this->configuration['table_name'])) { + if ($forConnection !== $this->driverConnection && !$isSameDatabase($this->executeStatement(...))) { return; } diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php index aeb689a8d0b4d..dac4dd538b731 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/DoctrineTransport.php @@ -79,10 +79,14 @@ public function setup(): void /** * Adds the Table to the Schema if this transport uses this connection. + * + * @param \Closure $isSameDatabase */ - public function configureSchema(Schema $schema, DbalConnection $forConnection): void + public function configureSchema(Schema $schema, DbalConnection $forConnection/* , \Closure $isSameDatabase */): void { - $this->connection->configureSchema($schema, $forConnection); + $isSameDatabase = 2 < \func_num_args() ? func_get_arg(2) : static fn () => false; + + $this->connection->configureSchema($schema, $forConnection, $isSameDatabase); } /** From 02f05bf63df9b50db0779b3e22b13fcda57b1d39 Mon Sep 17 00:00:00 2001 From: BASAK Semih Date: Thu, 15 Dec 2022 19:14:46 +0100 Subject: [PATCH 061/475] Use `::class` for Full Qualified Class Names --- .../Compiler/DumpDataCollectorPassTest.php | 9 +++++---- .../DebugExtensionTest.php | 3 ++- .../EventDispatcherDebugCommandTest.php | 5 +++-- ...AddExpressionLanguageProvidersPassTest.php | 8 ++++---- .../DataCollectorTranslatorPassTest.php | 11 +++++++---- .../Compiler/LoggingTranslatorPassTest.php | 3 ++- .../TestServiceContainerRefPassesTest.php | 3 ++- .../Fixtures/php/messenger_routing.php | 7 +++++-- .../messenger_routing_invalid_transport.php | 4 +++- .../Fixtures/php/messenger_routing_single.php | 4 +++- .../php/workflows_explicitly_enabled.php | 4 +++- ...ows_explicitly_enabled_named_workflows.php | 4 +++- .../FrameworkExtensionTest.php | 8 +++++--- .../AbstractAttributeRoutingTest.php | 4 +++- .../Tests/Functional/AbstractWebTestCase.php | 3 ++- .../Functional/ContainerDebugCommandTest.php | 3 ++- .../Functional/DebugAutowiringCommandTest.php | 16 ++++++++++------ .../Functional/SluggerLocaleAwareTest.php | 4 +++- .../SecurityDataCollectorTest.php | 2 +- ...AddExpressionLanguageProvidersPassTest.php | 8 ++++---- .../CompleteConfigurationTest.php | 7 ++++--- .../Tests/Functional/AbstractWebTestCase.php | 3 ++- .../Controller/LoginController.php | 3 ++- .../Form/UserLoginType.php | 9 ++++++--- .../FormLoginExtension.php | 3 ++- .../DependencyInjection/TwigExtensionTest.php | 13 ++++++++----- .../WebProfilerExtensionTest.php | 19 ++++++++++++------- 27 files changed, 108 insertions(+), 62 deletions(-) diff --git a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/Compiler/DumpDataCollectorPassTest.php b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/Compiler/DumpDataCollectorPassTest.php index 0b518ee00f71a..769a1421d9c7f 100644 --- a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/Compiler/DumpDataCollectorPassTest.php +++ b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/Compiler/DumpDataCollectorPassTest.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; class DumpDataCollectorPassTest extends TestCase { @@ -25,7 +26,7 @@ public function testProcessWithoutFileLinkFormatParameter() $container = new ContainerBuilder(); $container->addCompilerPass(new DumpDataCollectorPass()); - $definition = new Definition('Symfony\Component\HttpKernel\DataCollector\DumpDataCollector', [null, null, null, null]); + $definition = new Definition(DumpDataCollector::class, [null, null, null, null]); $container->setDefinition('data_collector.dump', $definition); $container->compile(); @@ -39,7 +40,7 @@ public function testProcessWithToolbarEnabled() $container->addCompilerPass(new DumpDataCollectorPass()); $requestStack = new RequestStack(); - $definition = new Definition('Symfony\Component\HttpKernel\DataCollector\DumpDataCollector', [null, null, null, $requestStack]); + $definition = new Definition(DumpDataCollector::class, [null, null, null, $requestStack]); $container->setDefinition('data_collector.dump', $definition); $container->setParameter('web_profiler.debug_toolbar.mode', WebDebugToolbarListener::ENABLED); @@ -53,7 +54,7 @@ public function testProcessWithToolbarDisabled() $container = new ContainerBuilder(); $container->addCompilerPass(new DumpDataCollectorPass()); - $definition = new Definition('Symfony\Component\HttpKernel\DataCollector\DumpDataCollector', [null, null, null, new RequestStack()]); + $definition = new Definition(DumpDataCollector::class, [null, null, null, new RequestStack()]); $container->setDefinition('data_collector.dump', $definition); $container->setParameter('web_profiler.debug_toolbar.mode', WebDebugToolbarListener::DISABLED); @@ -67,7 +68,7 @@ public function testProcessWithoutToolbar() $container = new ContainerBuilder(); $container->addCompilerPass(new DumpDataCollectorPass()); - $definition = new Definition('Symfony\Component\HttpKernel\DataCollector\DumpDataCollector', [null, null, null, new RequestStack()]); + $definition = new Definition(DumpDataCollector::class, [null, null, null, new RequestStack()]); $container->setDefinition('data_collector.dump', $definition); $container->compile(); diff --git a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php index f026d4d188232..7cdab627afd99 100644 --- a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php +++ b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\DebugBundle\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; +use Symfony\Bundle\DebugBundle\DebugBundle; use Symfony\Bundle\DebugBundle\DependencyInjection\DebugExtension; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -114,7 +115,7 @@ private function createContainer() 'kernel.charset' => 'UTF-8', 'kernel.debug' => true, 'kernel.project_dir' => __DIR__, - 'kernel.bundles' => ['DebugBundle' => 'Symfony\\Bundle\\DebugBundle\\DebugBundle'], + 'kernel.bundles' => ['DebugBundle' => DebugBundle::class], ])); return $container; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/EventDispatcherDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/EventDispatcherDebugCommandTest.php index a506ac2d2915f..c2057282e06b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/EventDispatcherDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/EventDispatcherDebugCommandTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Mailer\Event\MessageEvent; class EventDispatcherDebugCommandTest extends TestCase { @@ -33,7 +34,7 @@ public function testComplete(array $input, array $expectedSuggestions) public function provideCompletionSuggestions() { - yield 'event' => [[''], ['Symfony\Component\Mailer\Event\MessageEvent', 'console.command']]; + yield 'event' => [[''], [MessageEvent::class, 'console.command']]; yield 'event for other dispatcher' => [['--dispatcher', 'other_event_dispatcher', ''], ['other_event', 'App\OtherEvent']]; yield 'dispatcher' => [['--dispatcher='], ['event_dispatcher', 'other_event_dispatcher']]; yield 'format' => [['--format='], ['txt', 'xml', 'json', 'md']]; @@ -44,7 +45,7 @@ private function createCommandCompletionTester(): CommandCompletionTester $dispatchers = new ServiceLocator([ 'event_dispatcher' => function () { $dispatcher = new EventDispatcher(); - $dispatcher->addListener('Symfony\Component\Mailer\Event\MessageEvent', 'var_dump'); + $dispatcher->addListener(MessageEvent::class, 'var_dump'); $dispatcher->addListener('console.command', 'var_dump'); return $dispatcher; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php index 1207a4820d103..aaba736255f1d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php @@ -24,11 +24,11 @@ public function testProcessForRouter() $container = new ContainerBuilder(); $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); - $definition = new Definition('\stdClass'); + $definition = new Definition(\stdClass::class); $definition->addTag('routing.expression_language_provider'); $container->setDefinition('some_routing_provider', $definition->setPublic(true)); - $container->register('router.default', '\stdClass')->setPublic(true); + $container->register('router.default', \stdClass::class)->setPublic(true); $container->compile(); $router = $container->getDefinition('router.default'); @@ -43,11 +43,11 @@ public function testProcessForRouterAlias() $container = new ContainerBuilder(); $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); - $definition = new Definition('\stdClass'); + $definition = new Definition(\stdClass::class); $definition->addTag('routing.expression_language_provider'); $container->setDefinition('some_routing_provider', $definition->setPublic(true)); - $container->register('my_router', '\stdClass')->setPublic(true); + $container->register('my_router', \stdClass::class)->setPublic(true); $container->setAlias('router.default', 'my_router'); $container->compile(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php index 0167f55101b7b..769a59ba5200e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php @@ -15,6 +15,9 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Translation\DataCollector\TranslationDataCollector; +use Symfony\Component\Translation\DataCollectorTranslator; +use Symfony\Component\Translation\Translator; use Symfony\Contracts\Translation\TranslatorInterface; class DataCollectorTranslatorPassTest extends TestCase @@ -27,16 +30,16 @@ protected function setUp(): void $this->container = new ContainerBuilder(); $this->dataCollectorTranslatorPass = new DataCollectorTranslatorPass(); - $this->container->setParameter('translator_implementing_bag', 'Symfony\Component\Translation\Translator'); + $this->container->setParameter('translator_implementing_bag', Translator::class); $this->container->setParameter('translator_not_implementing_bag', 'Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\TranslatorWithTranslatorBag'); - $this->container->register('translator.data_collector', 'Symfony\Component\Translation\DataCollectorTranslator') + $this->container->register('translator.data_collector', DataCollectorTranslator::class) ->setPublic(false) ->setDecoratedService('translator') ->setArguments([new Reference('translator.data_collector.inner')]) ; - $this->container->register('data_collector.translation', 'Symfony\Component\Translation\DataCollector\TranslationDataCollector') + $this->container->register('data_collector.translation', TranslationDataCollector::class) ->setArguments([new Reference('translator.data_collector')]) ; } @@ -68,7 +71,7 @@ public function testProcessKeepsDataCollectorIfTranslatorImplementsTranslatorBag public function getImplementingTranslatorBagInterfaceTranslatorClassNames() { return [ - ['Symfony\Component\Translation\Translator'], + [Translator::class], ['%translator_implementing_bag%'], ]; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php index 6838d47883d77..9204212169a46 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php @@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Translation\Translator; class LoggingTranslatorPassTest extends TestCase { @@ -22,7 +23,7 @@ public function testProcess() { $container = new ContainerBuilder(); $container->setParameter('translator.logging', true); - $container->setParameter('translator.class', 'Symfony\Component\Translation\Translator'); + $container->setParameter('translator.class', Translator::class); $container->register('monolog.logger'); $container->setAlias('logger', 'monolog.logger'); $container->register('translator.default', '%translator.class%'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php index 7dc9e6f59ec99..d43605b8284bf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; @@ -58,7 +59,7 @@ public function testProcess() ]; $privateServices = $container->getDefinition('test.private_services_locator')->getArgument(0); - unset($privateServices['Symfony\Component\DependencyInjection\ContainerInterface'], $privateServices['Psr\Container\ContainerInterface']); + unset($privateServices[\Symfony\Component\DependencyInjection\ContainerInterface::class], $privateServices[ContainerInterface::class]); $this->assertEquals($expected, $privateServices); $this->assertFalse($container->getDefinition('Test\private_used_non_shared_service')->isShared()); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php index 77f4d5b93bebc..c045cace2589a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php @@ -1,5 +1,8 @@ loadFromExtension('framework', [ 'http_method_override' => false, 'serializer' => true, @@ -8,8 +11,8 @@ 'default_serializer' => 'messenger.transport.symfony_serializer', ], 'routing' => [ - 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage' => ['amqp', 'messenger.transport.audit'], - 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage' => [ + DummyMessage::class => ['amqp', 'messenger.transport.audit'], + SecondMessage::class => [ 'senders' => ['amqp', 'audit'], ], '*' => 'amqp', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_transport.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_transport.php index b552a3ebe5d5b..9bf91fd303d3a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_transport.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_transport.php @@ -1,5 +1,7 @@ loadFromExtension('framework', [ 'http_method_override' => false, 'serializer' => true, @@ -8,7 +10,7 @@ 'default_serializer' => 'messenger.transport.symfony_serializer', ], 'routing' => [ - 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage' => 'invalid', + DummyMessage::class => 'invalid', ], 'transports' => [ 'amqp' => 'amqp://localhost/%2f/messages', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_single.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_single.php index f487a0f8f90d9..a40347006646e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_single.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_single.php @@ -1,10 +1,12 @@ loadFromExtension('framework', [ 'http_method_override' => false, 'messenger' => [ 'routing' => [ - 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage' => ['amqp'], + DummyMessage::class => ['amqp'], ], 'transports' => [ 'amqp' => 'amqp://localhost/%2f/messages', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows_explicitly_enabled.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows_explicitly_enabled.php index ad71b2729853f..624389dd9ceac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows_explicitly_enabled.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows_explicitly_enabled.php @@ -1,12 +1,14 @@ loadFromExtension('framework', [ 'http_method_override' => false, 'workflows' => [ 'enabled' => true, 'foo' => [ 'type' => 'workflow', - 'supports' => ['Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest'], + 'supports' => [FrameworkExtensionTest::class], 'initial_marking' => ['bar'], 'places' => ['bar', 'baz'], 'transitions' => [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows_explicitly_enabled_named_workflows.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows_explicitly_enabled_named_workflows.php index 49ab48914e1ba..21ad8f6f42413 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows_explicitly_enabled_named_workflows.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows_explicitly_enabled_named_workflows.php @@ -1,12 +1,14 @@ loadFromExtension('framework', [ 'http_method_override' => false, 'workflows' => [ 'enabled' => true, 'workflows' => [ 'type' => 'workflow', - 'supports' => ['Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest'], + 'supports' => [FrameworkExtensionTest::class], 'initial_marking' => ['bar'], 'places' => ['bar', 'baz'], 'transitions' => [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 286586553493c..90228e9052723 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -17,6 +17,7 @@ use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Bundle\FullStack; @@ -70,6 +71,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\DependencyInjection\TranslatorPass; use Symfony\Component\Translation\LocaleSwitcher; @@ -1409,7 +1411,7 @@ public function testSerializerEnabled() $argument = $container->getDefinition('serializer.mapping.chain_loader')->getArgument(0); $this->assertCount(2, $argument); - $this->assertEquals('Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', $argument[0]->getClass()); + $this->assertEquals(AnnotationLoader::class, $argument[0]->getClass()); $this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1)); $this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3)); $this->assertArrayHasKey('circular_reference_handler', $container->getDefinition('serializer.normalizer.object')->getArgument(6)); @@ -1491,7 +1493,7 @@ public function testObjectNormalizerRegistered() $definition = $container->getDefinition('serializer.normalizer.object'); $tag = $definition->getTag('serializer.normalizer'); - $this->assertEquals('Symfony\Component\Serializer\Normalizer\ObjectNormalizer', $definition->getClass()); + $this->assertEquals(ObjectNormalizer::class, $definition->getClass()); $this->assertEquals(-1000, $tag[0]['priority']); } @@ -2217,7 +2219,7 @@ public function testNotifierWithSpecificMessageBus() protected function createContainer(array $data = []) { return new ContainerBuilder(new EnvPlaceholderParameterBag(array_merge([ - 'kernel.bundles' => ['FrameworkBundle' => 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle'], + 'kernel.bundles' => ['FrameworkBundle' => FrameworkBundle::class], 'kernel.bundles_metadata' => ['FrameworkBundle' => ['namespace' => 'Symfony\\Bundle\\FrameworkBundle', 'path' => __DIR__.'/../..']], 'kernel.cache_dir' => __DIR__, 'kernel.build_dir' => __DIR__, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractAttributeRoutingTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractAttributeRoutingTest.php index cf971d0d60dc3..8f8747bfff495 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractAttributeRoutingTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractAttributeRoutingTest.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use Symfony\Component\HttpFoundation\Request; + abstract class AbstractAttributeRoutingTest extends AbstractWebTestCase { /** @@ -32,7 +34,7 @@ public function testAnnotatedController(string $path, string $expectedValue) public function getRoutes(): array { return [ - ['/null_request', 'Symfony\Component\HttpFoundation\Request'], + ['/null_request', Request::class], ['/null_argument', ''], ['/null_argument_with_route_param', ''], ['/null_argument_with_route_param/value', 'value'], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php index bce53b8668251..085cb812eba69 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\app\AppKernel; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\KernelInterface; @@ -47,7 +48,7 @@ protected static function getKernelClass(): string { require_once __DIR__.'/app/AppKernel.php'; - return 'Symfony\Bundle\FrameworkBundle\Tests\Functional\app\AppKernel'; + return AppKernel::class; } protected static function createKernel(array $options = []): KernelInterface diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php index 949c6191a1252..32d8aa0a67073 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php @@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\BackslashClass; use Symfony\Component\Console\Tester\ApplicationTester; use Symfony\Component\Console\Tester\CommandCompletionTester; +use Symfony\Component\HttpKernel\HttpKernelInterface; /** * @group functional @@ -273,7 +274,7 @@ public function provideCompletionSuggestions() { $serviceId = 'console.command.container_debug'; $hiddenServiceId = '.console.command.container_debug.lazy'; - $interfaceServiceId = 'Symfony\Component\HttpKernel\HttpKernelInterface'; + $interfaceServiceId = HttpKernelInterface::class; yield 'name' => [ [''], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php index c3110cc71dcbb..650d2174d2096 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php @@ -11,10 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ClassAliasExampleClass; use Symfony\Component\Console\Tester\ApplicationTester; use Symfony\Component\Console\Tester\CommandCompletionTester; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Routing\RouterInterface; /** * @group functional @@ -31,7 +35,7 @@ public function testBasicFunctionality() $tester = new ApplicationTester($application); $tester->run(['command' => 'debug:autowiring']); - $this->assertStringContainsString('Symfony\Component\HttpKernel\HttpKernelInterface', $tester->getDisplay()); + $this->assertStringContainsString(HttpKernelInterface::class, $tester->getDisplay()); $this->assertStringContainsString('(http_kernel)', $tester->getDisplay()); } @@ -45,8 +49,8 @@ public function testSearchArgument() $tester = new ApplicationTester($application); $tester->run(['command' => 'debug:autowiring', 'search' => 'kern']); - $this->assertStringContainsString('Symfony\Component\HttpKernel\HttpKernelInterface', $tester->getDisplay()); - $this->assertStringNotContainsString('Symfony\Component\Routing\RouterInterface', $tester->getDisplay()); + $this->assertStringContainsString(HttpKernelInterface::class, $tester->getDisplay()); + $this->assertStringNotContainsString(RouterInterface::class, $tester->getDisplay()); } public function testSearchIgnoreBackslashWhenFindingService() @@ -58,7 +62,7 @@ public function testSearchIgnoreBackslashWhenFindingService() $tester = new ApplicationTester($application); $tester->run(['command' => 'debug:autowiring', 'search' => 'HttpKernelHttpKernelInterface']); - $this->assertStringContainsString('Symfony\Component\HttpKernel\HttpKernelInterface', $tester->getDisplay()); + $this->assertStringContainsString(HttpKernelInterface::class, $tester->getDisplay()); } public function testSearchNoResults() @@ -109,7 +113,7 @@ public function testNotConfusedByClassAliases() $tester = new ApplicationTester($application); $tester->run(['command' => 'debug:autowiring', 'search' => 'ClassAlias']); - $this->assertStringContainsString('Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ClassAliasExampleClass', $tester->getDisplay()); + $this->assertStringContainsString(ClassAliasExampleClass::class, $tester->getDisplay()); } /** @@ -131,6 +135,6 @@ public function testComplete(array $input, array $expectedSuggestions) public function provideCompletionSuggestions(): \Generator { - yield 'search' => [[''], ['SessionHandlerInterface', 'Psr\\Log\\LoggerInterface', 'Psr\\Container\\ContainerInterface $parameterBag']]; + yield 'search' => [[''], ['SessionHandlerInterface', LoggerInterface::class, 'Psr\\Container\\ContainerInterface $parameterBag']]; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SluggerLocaleAwareTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SluggerLocaleAwareTest.php index d8552977c0835..76901246138b6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SluggerLocaleAwareTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SluggerLocaleAwareTest.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Slugger\SlugConstructArgService; + /** * @group functional */ @@ -24,7 +26,7 @@ public function testLocalizedSlugger() $kernel = static::createKernel(['test_case' => 'Slugger', 'root_config' => 'config.yml']); $kernel->boot(); - $service = $kernel->getContainer()->get('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Slugger\SlugConstructArgService'); + $service = $kernel->getContainer()->get(SlugConstructArgService::class); $this->assertSame('Stoinostta-tryabva-da-bude-luzha', $service->hello()); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index b5178dcde56a1..9b5afb0b8b20a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -89,7 +89,7 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm $this->assertFalse($collector->isImpersonated()); $this->assertNull($collector->getImpersonatorUser()); $this->assertNull($collector->getImpersonationExitPath()); - $this->assertSame('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $collector->getTokenClass()->getValue()); + $this->assertSame(UsernamePasswordToken::class, $collector->getTokenClass()->getValue()); $this->assertTrue($collector->supportsRoleHierarchy()); $this->assertSame($normalizedRoles, $collector->getRoles()->getValue(true)); $this->assertSame($inheritedRoles, $collector->getInheritedRoles()->getValue(true)); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php index 6e79f8fc644d1..34dc4039d5655 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php @@ -24,11 +24,11 @@ public function testProcessForSecurity() $container = new ContainerBuilder(); $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); - $definition = new Definition('\stdClass'); + $definition = new Definition(\stdClass::class); $definition->addTag('security.expression_language_provider'); $container->setDefinition('some_security_provider', $definition->setPublic(true)); - $container->register('security.expression_language', '\stdClass')->setPublic(true); + $container->register('security.expression_language', \stdClass::class)->setPublic(true); $container->compile(); $calls = $container->getDefinition('security.expression_language')->getMethodCalls(); @@ -42,11 +42,11 @@ public function testProcessForSecurityAlias() $container = new ContainerBuilder(); $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); - $definition = new Definition('\stdClass'); + $definition = new Definition(\stdClass::class); $definition->addTag('security.expression_language_provider'); $container->setDefinition('some_security_provider', $definition->setPublic(true)); - $container->register('my_security.expression_language', '\stdClass')->setPublic(true); + $container->register('my_security.expression_language', \stdClass::class)->setPublic(true); $container->setAlias('security.expression_language', 'my_security.expression_language'); $container->compile(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index 2909627f2ce35..acf5de337fc6b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -30,6 +30,7 @@ use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy; +use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticatorManager; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; @@ -240,7 +241,7 @@ public function testFirewalls() ], ], $listeners); - $this->assertFalse($container->hasAlias('Symfony\Component\Security\Core\User\UserCheckerInterface', 'No user checker alias is registered when custom user checker services are registered')); + $this->assertFalse($container->hasAlias(UserCheckerInterface::class, 'No user checker alias is registered when custom user checker services are registered')); } public function testFirewallRequestMatchers() @@ -280,8 +281,8 @@ public function testUserCheckerAliasIsRegistered() { $container = $this->getContainer('no_custom_user_checker'); - $this->assertTrue($container->hasAlias('Symfony\Component\Security\Core\User\UserCheckerInterface', 'Alias for user checker is registered when no custom user checker service is registered')); - $this->assertFalse($container->getAlias('Symfony\Component\Security\Core\User\UserCheckerInterface')->isPublic()); + $this->assertTrue($container->hasAlias(UserCheckerInterface::class, 'Alias for user checker is registered when no custom user checker service is registered')); + $this->assertFalse($container->getAlias(UserCheckerInterface::class)->isPublic()); } public function testAccess() diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php index 9b79a4cd7a9e9..00c81bc380b2d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; +use Symfony\Bundle\SecurityBundle\Tests\Functional\app\AppKernel; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\KernelInterface; @@ -47,7 +48,7 @@ protected static function getKernelClass(): string { require_once __DIR__.'/app/AppKernel.php'; - return 'Symfony\Bundle\SecurityBundle\Tests\Functional\app\AppKernel'; + return AppKernel::class; } protected static function createKernel(array $options = []): KernelInterface diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php index 22f7de69b208c..42a87a1235937 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Controller; use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginType; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AccessDeniedException; @@ -29,7 +30,7 @@ public function __construct(ContainerInterface $container) public function loginAction() { - $form = $this->container->get('form.factory')->create('Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginType'); + $form = $this->container->get('form.factory')->create(UserLoginType::class); return new Response($this->container->get('twig')->render('@CsrfFormLogin/Login/login.html.twig', [ 'form' => $form->createView(), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php index 0f89b3c1b4fc8..80adb49a8892e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php @@ -12,6 +12,9 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; @@ -39,9 +42,9 @@ public function __construct(RequestStack $requestStack) public function buildForm(FormBuilderInterface $builder, array $options) { $builder - ->add('username', 'Symfony\Component\Form\Extension\Core\Type\TextType') - ->add('password', 'Symfony\Component\Form\Extension\Core\Type\PasswordType') - ->add('_target_path', 'Symfony\Component\Form\Extension\Core\Type\HiddenType') + ->add('username', TextType::class) + ->add('password', PasswordType::class) + ->add('_target_path', HiddenType::class) ; $request = $this->requestStack->getCurrentRequest(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functio 341A nal/Bundle/FormLoginBundle/DependencyInjection/FormLoginExtension.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/DependencyInjection/FormLoginExtension.php index 7cd7ad446f255..2c81ca6416171 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/DependencyInjection/FormLoginExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/DependencyInjection/FormLoginExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\DependencyInjection; +use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\Security\LocalizedFormFailureHandler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Reference; @@ -20,7 +21,7 @@ class FormLoginExtension extends Extension public function load(array $configs, ContainerBuilder $container) { $container - ->register('localized_form_failure_handler', 'Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\Security\LocalizedFormFailureHandler') + ->register('localized_form_failure_handler', LocalizedFormFailureHandler::class) ->addArgument(new Reference('router')) ; } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index bfb215b488715..5bc4e6cff02cb 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -24,7 +24,10 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\Form\FormRenderer; use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Stopwatch\Stopwatch; +use Twig\Environment; class TwigExtensionTest extends TestCase { @@ -35,7 +38,7 @@ public function testLoadEmptyConfiguration() $container->loadFromExtension('twig'); $this->compileContainer($container); - $this->assertEquals('Twig\Environment', $container->getDefinition('twig')->getClass(), '->load() loads the twig.xml file'); + $this->assertEquals(Environment::class, $container->getDefinition('twig')->getClass(), '->load() loads the twig.xml file'); $this->assertContains('form_div_layout.html.twig', $container->getParameter('twig.form.resources'), '->load() includes default template for form resources'); @@ -60,7 +63,7 @@ public function testLoadFullConfiguration($format) $this->loadFromFile($container, 'full', $format); $this->compileContainer($container); - $this->assertEquals('Twig\Environment', $container->getDefinition('twig')->getClass(), '->load() loads the twig.xml file'); + $this->assertEquals(Environment::class, $container->getDefinition('twig')->getClass(), '->load() loads the twig.xml file'); // Form resources $resources = $container->getParameter('twig.form.resources'); @@ -221,7 +224,7 @@ public function testStopwatchExtensionAvailability($debug, $stopwatchEnabled, $e $container = $this->createContainer(); $container->setParameter('kernel.debug', $debug); if ($stopwatchEnabled) { - $container->register('debug.stopwatch', 'Symfony\Component\Stopwatch\Stopwatch'); + $container->register('debug.stopwatch', Stopwatch::class); } $container->registerExtension(new TwigExtension()); $container->loadFromExtension('twig'); @@ -262,9 +265,9 @@ public function testRuntimeLoader() $loader = $container->getDefinition('twig.runtime_loader'); $args = $container->getDefinition((string) $loader->getArgument(0))->getArgument(0); - $this->assertArrayHasKey('Symfony\Component\Form\FormRenderer', $args); + $this->assertArrayHasKey(FormRenderer::class, $args); $this->assertArrayHasKey('FooClass', $args); - $this->assertEquals('twig.form.renderer', $args['Symfony\Component\Form\FormRenderer']->getValues()[0]); + $this->assertEquals('twig.form.renderer', $args[FormRenderer::class]->getValues()[0]); $this->assertEquals('foo', $args['FooClass']->getValues()[0]); } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php index 0038736820388..fa0c9baf63d55 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php @@ -22,6 +22,11 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface; +use Symfony\Component\Routing\RouterInterface; +use Twig\Environment; +use Twig\Loader\ArrayLoader; class WebProfilerExtensionTest extends TestCase { @@ -55,10 +60,10 @@ protected function setUp(): void $this->container->register('data_collector.dump', DumpDataCollector::class)->setPublic(true); $this->container->register('error_handler.error_renderer.html', HtmlErrorRenderer::class)->setPublic(true); $this->container->register('event_dispatcher', EventDispatcher::class)->setPublic(true); - $this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'))->setPublic(true); - $this->container->register('twig', 'Twig\Environment')->setPublic(true); - $this->container->register('twig_loader', 'Twig\Loader\ArrayLoader')->addArgument([])->setPublic(true); - $this->container->register('twig', 'Twig\Environment')->addArgument(new Reference('twig_loader'))->setPublic(true); + $this->container->register('router', $this->getMockClass(RouterInterface::class))->setPublic(true); + $this->container->register('twig', Environment::class)->setPublic(true); + $this->container->register('twig_loader', ArrayLoader::class)->addArgument([])->setPublic(true); + $this->container->register('twig', Environment::class)->addArgument(new Reference('twig_loader'))->setPublic(true); $this->container->setParameter('kernel.bundles', []); $this->container->setParameter('kernel.cache_dir', __DIR__); $this->container->setParameter('kernel.build_dir', __DIR__); @@ -66,10 +71,10 @@ protected function setUp(): void $this->container->setParameter('kernel.project_dir', __DIR__); $this->container->setParameter('kernel.charset', 'UTF-8'); $this->container->setParameter('debug.file_link_format', null); - $this->container->setParameter('profiler.class', ['Symfony\\Component\\HttpKernel\\Profiler\\Profiler']); - $this->container->register('profiler', $this->getMockClass('Symfony\\Component\\HttpKernel\\Profiler\\Profiler')) + $this->container->setParameter('profiler.class', [Profiler::class]); + $this->container->register('profiler', $this->getMockClass(Profiler::class)) ->setPublic(true) - ->addArgument(new Definition($this->getMockClass('Symfony\\Component\\HttpKernel\\Profiler\\ProfilerStorageInterface'))); + ->addArgument(new Definition($this->getMockClass(ProfilerStorageInterface::class))); $this->container->setParameter('data_collector.templates', []); $this->container->set('kernel', $this->kernel); $this->container->addCompilerPass(new RegisterListenersPass()); From a7926b2d83f35fe53c41a28d8055490cc1955928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 12 Dec 2022 15:43:44 +0100 Subject: [PATCH 062/475] [Messenger] Move Transport/InMemoryTransport to Transport/InMemory/InMemoryTransport --- UPGRADE-6.3.md | 8 + UPGRADE-7.0.md | 8 + .../Resources/config/messenger.php | 2 +- .../Bundle/FrameworkBundle/composer.json | 4 +- src/Symfony/Component/Messenger/CHANGELOG.md | 8 + .../InMemoryTransportFactoryTest.php | 6 +- .../{ => InMemory}/InMemoryTransportTest.php | 4 +- .../Transport/InMemory/InMemoryTransport.php | 145 ++++++++++++++++++ .../InMemory/InMemoryTransportFactory.php | 59 +++++++ .../Messenger/Transport/InMemoryTransport.php | 131 +--------------- .../Transport/InMemoryTransportFactory.php | 44 +----- 11 files changed, 246 insertions(+), 173 deletions(-) rename src/Symfony/Component/Messenger/Tests/Transport/{ => InMemory}/InMemoryTransportFactoryTest.php (93%) rename src/Symfony/Component/Messenger/Tests/Transport/{ => InMemory}/InMemoryTransportTest.php (97%) create mode 100644 src/Symfony/Component/Messenger/Transport/InMemory/InMemoryTransport.php create mode 100644 src/Symfony/Component/Messenger/Transport/InMemory/InMemoryTransportFactory.php diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index 17e9002f0882e..77c7858198d80 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -11,6 +11,14 @@ HttpKernel * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead +Messenger +--------- + + * Deprecate `Symfony\Component\Messenger\Transport\InMemoryTransport` and + `Symfony\Component\Messenger\Transport\InMemoryTransportFactory` in favor of + `Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport` and + `Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory` + SecurityBundle -------------- diff --git a/UPGRADE-7.0.md b/UPGRADE-7.0.md index 587796bbb6e9f..42279c090c308 100644 --- a/UPGRADE-7.0.md +++ b/UPGRADE-7.0.md @@ -1,6 +1,14 @@ UPGRADE FROM 6.4 to 7.0 ======================= +Messenger +--------- + + * Remove `Symfony\Component\Messenger\Transport\InMemoryTransport` and + `Symfony\Component\Messenger\Transport\InMemoryTransportFactory` in favor of + `Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport` and + `Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory` + Workflow -------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php index 13e8dad627835..39030ce51132e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php @@ -35,7 +35,7 @@ use Symfony\Component\Messenger\Middleware\ValidationMiddleware; use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy; use Symfony\Component\Messenger\RoutableMessageBus; -use Symfony\Component\Messenger\Transport\InMemoryTransportFactory; +use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory; use Symfony\Component\Messenger\Transport\Sender\SendersLocator; use Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer; use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 509d4278daca6..0f0645331f774 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -48,7 +48,7 @@ "symfony/http-client": "^5.4|^6.0", "symfony/lock": "^5.4|^6.0", "symfony/mailer": "^5.4|^6.0", - "symfony/messenger": "^6.2", + "symfony/messenger": "^6.3", "symfony/mime": "^6.2", "symfony/notifier": "^5.4|^6.0", "symfony/process": "^5.4|^6.0", @@ -83,7 +83,7 @@ "symfony/form": "<5.4", "symfony/lock": "<5.4", "symfony/mailer": "<5.4", - "symfony/messenger": "<6.2", + "symfony/messenger": "<6.3", "symfony/mime": "<6.2", "symfony/property-info": "<5.4", "symfony/property-access": "<5.4", diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index 650e2ff0ac44f..4e2f52ebf2ff3 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate `Symfony\Component\Messenger\Transport\InMemoryTransport` and + `Symfony\Component\Messenger\Transport\InMemoryTransportFactory` in favor of + `Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport` and + `Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory` + 6.2 --- diff --git a/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportFactoryTest.php b/src/Symfony/Component/Messenger/Tests/Transport/InMemory/InMemor F438 yTransportFactoryTest.php similarity index 93% rename from src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportFactoryTest.php rename to src/Symfony/Component/Messenger/Tests/Transport/InMemory/InMemoryTransportFactoryTest.php index d29ab10639da2..2be1ee57a0bd9 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportFactoryTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/InMemory/InMemoryTransportFactoryTest.php @@ -9,14 +9,14 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Messenger\Tests\Transport; +namespace Symfony\Component\Messenger\Tests\Transport\InMemory; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; -use Symfony\Component\Messenger\Transport\InMemoryTransport; -use Symfony\Component\Messenger\Transport\InMemoryTransportFactory; +use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport; +use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; /** diff --git a/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportTest.php b/src/Symfony/Component/Messenger/Tests/Transport/InMemory/InMemoryTransportTest.php similarity index 97% rename from src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportTest.php rename to src/Symfony/Component/Messenger/Tests/Transport/InMemory/InMemoryTransportTest.php index 1b10a6165823a..97e7f227a50e1 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/InMemory/InMemoryTransportTest.php @@ -9,14 +9,14 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Messenger\Tests\Transport; +namespace Symfony\Component\Messenger\Tests\Transport\InMemory; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp; use Symfony\Component\Messenger\Tests\Fixtures\AnEnvelopeStamp; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; -use Symfony\Component\Messenger\Transport\InMemoryTransport; +use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; /** diff --git a/src/Symfony/Component/Messenger/Transport/InMemory/InMemoryTransport.php b/src/Symfony/Component/Messenger/Transport/InMemory/InMemoryTransport.php new file mode 100644 index 0000000000000..40132068aecae --- /dev/null +++ b/src/Symfony/Component/Messenger/Transport/InMemory/InMemoryTransport.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Transport\InMemory; + +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\LogicException; +use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp; +use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; +use Symfony\Component\Messenger\Transport\TransportInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Transport that stays in memory. Useful for testing purpose. + * + * @author Gary PEGEOT + */ +class InMemoryTransport implements TransportInterface, ResetInterface +{ + /** + * @var Envelope[] + */ + private array $sent = []; + + /** + * @var Envelope[] + */ + private array $acknowledged = []; + + /** + * @var Envelope[] + */ + private array $rejected = []; + + /** + * @var Envelope[] + */ + private array $queue = []; + + private int $nextId = 1; + private ?SerializerInterface $serializer; + + public function __construct(SerializerInterface $serializer = null) + { + $this->serializer = $serializer; + } + + public function get(): iterable + { + return array_values($this->decode($this->queue)); + } + + public function ack(Envelope $envelope): void + { + $this->acknowledged[] = $this->encode($envelope); + + if (!$transportMessageIdStamp = $envelope->last(TransportMessageIdStamp::class)) { + throw new LogicException('No TransportMessageIdStamp found on the Envelope.'); + } + + unset($this->queue[$transportMessageIdStamp->getId()]); + } + + public function reject(Envelope $envelope): void + { + $this->rejected[] = $this->encode($envelope); + + if (!$transportMessageIdStamp = $envelope->last(TransportMessageIdStamp::class)) { + throw new LogicException('No TransportMessageIdStamp found on the Envelope.'); + } + + unset($this->queue[$transportMessageIdStamp->getId()]); + } + + public function send(Envelope $envelope): Envelope + { + $id = $this->nextId++; + $envelope = $envelope->with(new TransportMessageIdStamp($id)); + $encodedEnvelope = $this->encode($envelope); + $this->sent[] = $encodedEnvelope; + $this->queue[$id] = $encodedEnvelope; + + return $envelope; + } + + public function reset() + { + $this->sent = $this->queue = $this->rejected = $this->acknowledged = []; + } + + /** + * @return Envelope[] + */ + public function getAcknowledged(): array + { + return $this->decode($this->acknowledged); + } + + /** + * @return Envelope[] + */ + public function getRejected(): array + { + return $this->decode($this->rejected); + } + + /** + * @return Envelope[] + */ + public function getSent(): array + { + return $this->decode($this->sent); + } + + private function encode(Envelope $envelope): Envelope|array + { + if (null === $this->serializer) { + return $envelope; + } + + return $this->serializer->encode($envelope); + } + + /** + * @param array $messagesEncoded + * + * @return Envelope[] + */ + private function decode(array $messagesEncoded): array + { + if (null === $this->serializer) { + return $messagesEncoded; + } + + return array_map($this->serializer->decode(...), $messagesEncoded); + } +} diff --git a/src/Symfony/Component/Messenger/Transport/InMemory/InMemoryTransportFactory.php b/src/Symfony/Component/Messenger/Transport/InMemory/InMemoryTransportFactory.php new file mode 100644 index 0000000000000..3e704b1bb3528 --- /dev/null +++ b/src/Symfony/Component/Messenger/Transport/InMemory/InMemoryTransportFactory.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Transport\InMemory; + +use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; +use Symfony\Component\Messenger\Transport\TransportFactoryInterface; +use Symfony\Component\Messenger\Transport\TransportInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Gary PEGEOT + */ +class InMemoryTransportFactory implements TransportFactoryInterface, ResetInterface +{ + /** + * @var InMemoryTransport[] + */ + private array $createdTransports = []; + + public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface + { + ['serialize' => $serialize] = $this->parseDsn($dsn); + + return $this->createdTransports[] = new InMemoryTransport($serialize ? $serializer : null); + } + + public function supports(string $dsn, array $options): bool + { + return str_starts_with($dsn, 'in-memory://'); + } + + public function reset() + { + foreach ($this->createdTransports as $transport) { + $transport->reset(); + } + } + + private function parseDsn(string $dsn): array + { + $query = []; + if ($queryAsString = strstr($dsn, '?')) { + parse_str(ltrim($queryAsString, '?'), $query); + } + + return [ + 'serialize' => filter_var($query['serialize'] ?? false, \FILTER_VALIDATE_BOOL), + ]; + } +} diff --git a/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php b/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php index 68ec8b619eb77..a8941b00b67ad 100644 --- a/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php +++ b/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php @@ -11,134 +11,13 @@ namespace Symfony\Component\Messenger\Transport; -use Symfony\Component\Messenger\Envelope; -use Symfony\Component\Messenger\Exception\LogicException; -use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp; -use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; -use Symfony\Contracts\Service\ResetInterface; +use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport as BaseInMemoryTransport; + +trigger_deprecation('symfony/messenger', '6.3', 'The "%s" class is deprecated, use "%s" instead. ', InMemoryTransport::class, BaseInMemoryTransport::class); /** - * Transport that stays in memory. Useful for testing purpose. - * - * @author Gary PEGEOT + * @deprecated since Symfony 6.3, use {@link BaseInMemoryTransport} instead */ -class InMemoryTransport implements TransportInterface, ResetInterface +class InMemoryTransport extends BaseInMemoryTransport { - /** - * @var Envelope[] - */ - private array $sent = []; - - /** - * @var Envelope[] - */ - private array $acknowledged = []; - - /** - * @var Envelope[] - */ - private array $rejected = []; - - /** - * @var Envelope[] - */ - private array $queue = []; - - private int $nextId = 1; - private ?SerializerInterface $serializer; - - public function __construct(SerializerInterface $serializer = null) - { - $this->serializer = $serializer; - } - - public function get(): iterable - { - return array_values($this->decode($this->queue)); - } - - public function ack(Envelope $envelope): void - { - $this->acknowledged[] = $this->encode($envelope); - - if (!$transportMessageIdStamp = $envelope->last(TransportMessageIdStamp::class)) { - throw new LogicException('No TransportMessageIdStamp found on the Envelope.'); - } - - unset($this->queue[$transportMessageIdStamp->getId()]); - } - - public function reject(Envelope $envelope): void - { - $this->rejected[] = $this->encode($envelope); - - if (!$transportMessageIdStamp = $envelope->last(TransportMessageIdStamp::class)) { - throw new LogicException('No TransportMessageIdStamp found on the Envelope.'); - } - - unset($this->queue[$transportMessageIdStamp->getId()]); - } - - public function send(Envelope $envelope): Envelope - { - $id = $this->nextId++; - $envelope = $envelope->with(new TransportMessageIdStamp($id)); - $encodedEnvelope = $this->encode($envelope); - $this->sent[] = $encodedEnvelope; - $this->queue[$id] = $encodedEnvelope; - - return $envelope; - } - - public function reset() - { - $this->sent = $this->queue = $this->rejected = $this->acknowledged = []; - } - - /** - * @return Envelope[] - */ - public function getAcknowledged(): array - { - return $this->decode($this->acknowledged); - } - - /** - * @return Envelope[] - */ - public function getRejected(): array - { - return $this->decode($this->rejected); - } - - /** - * @return Envelope[] - */ - public function getSent(): array - { - return $this->decode($this->sent); - } - - private function encode(Envelope $envelope): Envelope|array - { - if (null === $this->serializer) { - return $envelope; - } - - return $this->serializer->encode($envelope); - } - - /** - * @param array $messagesEncoded - * - * @return Envelope[] - */ - private function decode(array $messagesEncoded): array - { - if (null === $this->serializer) { - return $messagesEncoded; - } - - return array_map($this->serializer->decode(...), $messagesEncoded); - } } diff --git a/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php b/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php index 232b941c2970c..bdd4817d6a600 100644 --- a/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php +++ b/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php @@ -11,47 +11,13 @@ namespace Symfony\Component\Messenger\Transport; -use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; -use Symfony\Contracts\Service\ResetInterface; +use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory as BaseInMemoryTransportFactory; + +trigger_deprecation('symfony/messenger', '6.3', 'The "%s" class is deprecated, use "%s" instead. ', InMemoryTransportFactory::class, BaseInMemoryTransportFactory::class); /** - * @author Gary PEGEOT + * @deprecated since Symfony 6.3, use {@link BaseInMemoryTransportFactory} instead */ -class InMemoryTransportFactory implements TransportFactoryInterface, ResetInterface +class InMemoryTransportFactory extends BaseInMemoryTransportFactory { - /** - * @var InMemoryTransport[] - */ - private array $createdTransports = []; - - public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface - { - ['serialize' => $serialize] = $this->parseDsn($dsn); - - return $this->createdTransports[] = new InMemoryTransport($serialize ? $serializer : null); - } - - public function supports(string $dsn, array $options): bool - { - return str_starts_with($dsn, 'in-memory://'); - } - - public function reset() - { - foreach ($this->createdTransports as $transport) { - $transport->reset(); - } - } - - private function parseDsn(string $dsn): array - { - $query = []; - if ($queryAsString = strstr($dsn, '?')) { - parse_str(ltrim($queryAsString, '?'), $query); - } - - return [ - 'serialize' => filter_var($query['serialize'] ?? false, \FILTER_VALIDATE_BOOL), - ]; - } } From bb6e4da210a89cc4f7be16cb307d48452a84f63f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 16 Dec 2022 17:04:38 +0100 Subject: [PATCH 063/475] Fix merge --- .../Cache/Tests/Adapter/DoctrineDbalAdapterTest.php | 6 +++--- .../Bridge/Doctrine/Tests/Transport/ConnectionTest.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php index 67f14fd66b51a..e289d039f3f36 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php @@ -79,7 +79,7 @@ public function testConfigureSchema() $schema = new Schema(); $adapter = new DoctrineDbalAdapter($connection); - $adapter->configureSchema($schema, $connection); + $adapter->configureSchema($schema, $connection, fn() => true); $this->assertTrue($schema->hasTable('cache_items')); } @@ -89,7 +89,7 @@ public function testConfigureSchemaDifferentDbalConnection() $schema = new Schema(); $adapter = $this->createCachePool(); - $adapter->configureSchema($schema, $otherConnection); + $adapter->configureSchema($schema, $otherConnection, fn () => false); $this->assertFalse($schema->hasTable('cache_items')); } @@ -100,7 +100,7 @@ public function testConfigureSchemaTableExists() $schema->createTable('cache_items'); $adapter = new DoctrineDbalAdapter($connection); - $adapter->configureSchema($schema, $connection); + $adapter->configureSchema($schema, $connection, fn() => true); $table = $schema->getTable('cache_items'); $this->assertEmpty($table->getColumns(), 'The table was not overwritten'); } diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php index 5795237bf207c..7e79c32d22248 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php @@ -422,7 +422,7 @@ public function testConfigureSchemaDifferentDbalConnection() $schema = new Schema(); $connection = new Connection([], $driverConnection); - $connection->configureSchema($schema, $driverConnection2, fn() => true); + $connection->configureSchema($schema, $driverConnection2, fn() => false); $this->assertFalse($schema->hasTable('messenger_messages')); } From 2ad8bb1020be39bd0007023b844b85a69931bf0e Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sat, 17 Dec 2022 12:53:41 +0100 Subject: [PATCH 064/475] [Console] Fix a test when pcntl is not available --- src/Symfony/Component/Console/Tests/ApplicationTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index d56492d84ee1c..7d24dec8b8478 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -2036,6 +2036,10 @@ public function testSignalableCommandHandlerCalledAfterEventListener() public function testSignalableCommandDoesNotInterruptedOnTermSignals() { + if (!\defined('SIGINT')) { + $this->markTestSkipped('SIGINT not available'); + } + $command = new TerminatableCommand(true, \SIGINT); $command->exitCode = 129; From 58d0662720ce12e36b43bc72e10e58e691122e8b Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sun, 21 Aug 2022 16:43:13 +0200 Subject: [PATCH 065/475] [HttpKernel] FileProfilerStorage remove expired profiles mechanism --- src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../Profiler/FileProfilerStorage.php | 31 ++++++++++- .../Profiler/FileProfilerStorageTest.php | 51 +++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 3122668e697cb..f409a6f13c06c 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead + * `FileProfilerStorage` removes profiles automatically after two days 6.2 --- diff --git a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php index d3c801de5b017..0870b6a241d1e 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php @@ -165,11 +165,15 @@ public function write(Profile $profile): bool $profile->getIp(), $profile->getMethod(), $profile->getUrl(), - $profile->getTime(), + $profile->getTime() ?: time(), $profile->getParentToken(), $profile->getStatusCode(), ]); fclose($file); + + if (1 === mt_rand(1, 10)) { + $this->removeExpiredProfiles(); + } } return true; @@ -289,4 +293,29 @@ private function doRead($token, Profile $profile = null): ?Profile return $this->createProfileFromData($token, $data, $profile); } + + private function removeExpiredProfiles() + { + $minimalProfileTimestamp = time() - 2 * 86400; + $file = $this->getIndexFilename(); + $handle = fopen($file, 'r'); + + if ($offset = is_file($file.'.offset') ? (int) file_get_contents($file.'.offset') : 0) { + fseek($handle, $offset); + } + + while ($line = fgets($handle)) { + [$csvToken, , , , $csvTime] = str_getcsv($line); + + if ($csvTime >= $minimalProfileTimestamp) { + break; + } + + @unlink($this->getFilename($csvToken)); + $offset += \strlen($line); + } + fclose($handle); + + file_put_contents($file.'.offset', $offset); + } } diff --git a/src/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php b/src/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php index 4eedd907de327..004b4d61f1f3e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php @@ -344,6 +344,57 @@ public function testMultiRowIndexFile() $this->assertFalse(fgetcsv($handle)); } + /** + * @dataProvider provideExpiredProfiles + */ + public function testRemoveExpiredProfiles(string $index, string $expectedOffset) + { + $file = $this->tmpDir.'/index.csv'; + file_put_contents($file, $index); + + $r = new \ReflectionMethod($this->storage, 'removeExpiredProfiles'); + $r->invoke($this->storage); + + $this->assertSame($expectedOffset, file_get_contents($this->tmpDir.'/index.csv.offset')); + } + + public static function provideExpiredProfiles() + { + $oneHourAgo = new \DateTimeImmutable('-1 hour'); + + yield 'One unexpired profile' => [ + <<getTimestamp()},, + + CSV, + '0', + ]; + + $threeDaysAgo = new \DateTimeImmutable('-3 days'); + + yield 'One expired profile' => [ + <<getTimestamp()},, + + CSV, + '48', + ]; + + $fourDaysAgo = new \DateTimeImmutable('-4 days'); + $threeDaysAgo = new \DateTimeImmutable('-3 days'); + $oneHourAgo = new \DateTimeImmutable('-1 hour'); + + yield 'Multiple expired profiles' => [ + <<getTimestamp()},, + token1,127.0.0.1,,http://foo.bar/1,{$threeDaysAgo->getTimestamp()},, + token2,127.0.0.2,,http://foo.bar/2,{$oneHourAgo->getTimestamp()},, + + CSV, + '96', + ]; + } + public function testReadLineFromFile() { $r = new \ReflectionMethod($this->storage, 'readLineFromFile'); From 6479653e7e283e4b00b24073867086d492b254e2 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Fri, 11 Nov 2022 18:56:59 +0100 Subject: [PATCH 066/475] [Security] Allow custom user identifier for X509 authenticator --- .../Security/Factory/X509Factory.php | 2 ++ .../config/security_authenticator.php | 1 + .../Http/Authenticator/X509Authenticator.php | 10 ++++--- .../Authenticator/X509AuthenticatorTest.php | 29 +++++++++++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php index f59783defd11c..77840005c5fc9 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php @@ -36,6 +36,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal ->replaceArgument(2, $firewallName) ->replaceArgument(3, $config['user']) ->replaceArgument(4, $config['credentials']) + ->replaceArgument(6, $config['user_identifier']) ; return $authenticatorId; @@ -58,6 +59,7 @@ public function addConfiguration(NodeDefinition $node) ->scalarNode('provider')->end() ->scalarNode('user')->defaultValue('SSL_CLIENT_S_DN_Email')->end() ->scalarNode('credentials')->defaultValue('SSL_CLIENT_S_DN')->end() + ->scalarNode('user_identifier')->defaultValue('emailAddress')->end() ->end() ; } diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php index 58be697595d42..92c91e989779c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php @@ -149,6 +149,7 @@ abstract_arg('user key'), abstract_arg('credentials key'), service('logger')->nullOnInvalid(), + abstract_arg('credentials user identifier'), ]) ->tag('monolog.logger', ['channel' => 'security']) diff --git a/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php b/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php index 5620d3921b0d0..227d4701190be 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/X509Authenticator.php @@ -30,13 +30,15 @@ class X509Authenticator extends AbstractPreAuthenticatedAuthenticator { private string $userKey; private string $credentialsKey; + private string $credentialUserIdentifier; - public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'SSL_CLIENT_S_DN_Email', string $credentialsKey = 'SSL_CLIENT_S_DN', LoggerInterface $logger = null) + public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'SSL_CLIENT_S_DN_Email', string $credentialsKey = 'SSL_CLIENT_S_DN', LoggerInterface $logger = null, string $credentialUserIdentifier = 'emailAddress') { parent::__construct($userProvider, $tokenStorage, $firewallName, $logger); $this->userKey = $userKey; $this->credentialsKey = $credentialsKey; + $this->credentialUserIdentifier = $credentialUserIdentifier; } protected function extractUsername(Request $request): string @@ -46,13 +48,13 @@ protected function extractUsername(Request $request): string $username = $request->server->get($this->userKey); } elseif ( $request->server->has($this->credentialsKey) - && preg_match('#emailAddress=([^,/@]++@[^,/]++)#', $request->server->get($this->credentialsKey), $matches) + && preg_match('#'.preg_quote($this->credentialUserIdentifier, '#').'=([^,/]++)#', $request->server->get($this->credentialsKey), $matches) ) { - $username = $matches[1]; + $username = trim($matches[1]); } if (null === $username) { - throw new BadCredentialsException(sprintf('SSL credentials not found: %s, %s', $this->userKey, $this->credentialsKey)); + throw new BadCredentialsException(sprintf('SSL credentials not found: "%s", "%s".', $this->userKey, $this->credentialsKey)); } return $username; diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/X509AuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/X509AuthenticatorTest.php index 7ee6aaa973894..9b764d3750a63 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/X509AuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/X509AuthenticatorTest.php @@ -120,6 +120,35 @@ public function testAuthenticationCustomCredentialsKey() $this->assertEquals('cert@example.com', $passport->getUser()->getUserIdentifier()); } + /** + * @dataProvider provideServerVarsUserIdentifier + */ + public function testAuthenticationCustomCredentialsUserIdentifier($username, $credentials) + { + $authenticator = new X509Authenticator($this->userProvider, new TokenStorage(), 'main', 'SSL_CLIENT_S_DN_Email', 'SSL_CLIENT_S_DN', null, 'CN'); + + $request = $this->createRequest([ + 'SSL_CLIENT_S_DN' => $credentials, + ]); + $this->assertTrue($authenticator->supports($request)); + + $this->userProvider->createUser(new InMemoryUser($username, null)); + + $passport = $authenticator->authenticate($request); + $this->assertEquals($username, $passport->getUser()->getUserIdentifier()); + } + + public static function provideServerVarsUserIdentifier() + { + yield ['Sample certificate DN', 'CN=Sample certificate DN/emailAddress=cert@example.com']; + yield ['Sample certificate DN', 'CN=Sample certificate DN/emailAddress=cert+something@example.com']; + yield ['Sample certificate DN', 'CN=Sample certificate DN,emailAddress=cert@example.com']; + yield ['Sample certificate DN', 'CN=Sample certificate DN,emailAddress=cert+something@example.com']; + yield ['Sample certificate DN', 'emailAddress=cert+something@example.com,CN=Sample certificate DN']; + yield ['Firstname.Lastname', 'emailAddress=firstname.lastname@mycompany.co.uk,CN=Firstname.Lastname,OU=london,OU=company design and engineering,OU=Issuer London,OU=Roaming,OU=Interactive,OU=Users,OU=Standard,OU=Business,DC=england,DC=core,DC=company,DC=co,DC=uk']; + yield ['user1', 'C=FR, O=My Organization, CN=user1, emailAddress=user1@myorg.fr']; + } + private function createRequest(array $server) { return new Request([], [], [], [], [], $server); From ce458c6b59f68d8aaab0fdb65386a6e8152c7fac Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sat, 29 Oct 2022 17:57:17 +0200 Subject: [PATCH 067/475] [SecurityBundle] Set request stateless when firewall is stateless --- src/Symfony/Bundle/SecurityBundle/CHANGELOG.md | 1 + .../Bundle/SecurityBundle/Security/FirewallMap.php | 9 ++++++++- .../SecurityBundle/Tests/Security/FirewallMapTest.php | 6 ++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index a1ffdb0349c3a..9deb248e1365f 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Deprecate enabling bundle and not configuring it + * Add `_stateless` attribute to the request when firewall is stateless 6.2 --- diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php index 21e5b8aa68279..d0151d10f9a28 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php @@ -72,7 +72,14 @@ private function getFirewallContext(Request $request): ?FirewallContext if (null === $requestMatcher || $requestMatcher->matches($request)) { $request->attributes->set('_firewall_context', $contextId); - return $this->container->get($contextId); + /** @var FirewallContext $context */ + $context = $this->container->get($contextId); + + if ($context->getConfig()?->isStateless()) { + $request->attributes->set('_stateless', true); + } + + return $context; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php index d174e13b5cff8..4acad02e65225 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php @@ -54,6 +54,7 @@ public function testGetListenersWithInvalidParameter() $this->assertEquals([[], null, null], $firewallMap->getListeners($request)); $this->assertNull($firewallMap->getFirewallConfig($request)); $this->assertFalse($request->attributes->has(self::ATTRIBUTE_FIREWALL_CONTEXT)); + $this->assertFalse($request->attributes->has('_stateless')); } public function testGetListeners() @@ -62,8 +63,8 @@ public function testGetListeners() $firewallContext = $this->createMock(FirewallContext::class); - $firewallConfig = new FirewallConfig('main', 'user_checker'); - $firewallContext->expects($this->once())->method('getConfig')->willReturn($firewallConfig); + $firewallConfig = new FirewallConfig('main', 'user_checker', null, true, true); + $firewallContext->expects($this->exactly(2))->method('getConfig')->willReturn($firewallConfig); $listener = function () {}; $firewallContext->expects($this->once())->method('getListeners')->willReturn([$listener]); @@ -88,5 +89,6 @@ public function testGetListeners() $this->assertEquals([[$listener], $exceptionListener, $logoutListener], $firewallMap->getListeners($request)); $this->assertEquals($firewallConfig, $firewallMap->getFirewallConfig($request)); $this->assertEquals('security.firewall.map.context.foo', $request->attributes->get(self::ATTRIBUTE_FIREWALL_CONTEXT)); + $this->assertTrue($request->attributes->get('_stateless')); } } From 5464c5732ce090bacd33a106ed457ddbca47fc5a Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 10 Dec 2022 17:47:26 +0100 Subject: [PATCH 068/475] [SecurityBundle] Improve support for authenticators that don't need a user provider Ref https://github.com/symfony/symfony/pull/48285 --- .../Bundle/SecurityBundle/CHANGELOG.md | 1 + .../Security/Factory/AccessTokenFactory.php | 6 ++-- ...StatelessAuthenticatorFactoryInterface.php | 28 +++++++++++++++++++ .../DependencyInjection/SecurityExtension.php | 28 ++++++++++++++----- .../Tests/Functional/AccessTokenTest.php | 12 ++++++++ .../Security/Handler/AccessTokenHandler.php | 6 ++-- .../config_self_contained_token.yml | 26 +++++++++++++++++ 7 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/StatelessAuthenticatorFactoryInterface.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_self_contained_token.yml diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 9deb248e1365f..ecf3804fa5253 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Deprecate enabling bundle and not configuring it * Add `_stateless` attribute to the request when firewall is stateless + * Add `StatelessAuthenticatorFactoryInterface` for authenticators targeting `stateless` firewalls only and that don't require a user provider 6.2 --- diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php index 2fbf3b2f8b567..a3ed0e3ee0839 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php @@ -23,7 +23,7 @@ * * @internal */ -final class AccessTokenFactory extends AbstractFactory +final class AccessTokenFactory extends AbstractFactory implements StatelessAuthenticatorFactoryInterface { private const PRIORITY = -40; @@ -67,7 +67,7 @@ public function getKey(): string return 'access_token'; } - public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string { $successHandler = isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null; $failureHandler = isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null; @@ -78,7 +78,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.access_token')) ->replaceArgument(0, new Reference($config['token_handler'])) ->replaceArgument(1, new Reference($extractorId)) - ->replaceArgument(2, new Reference($userProviderId)) + ->replaceArgument(2, $userProviderId ? new Reference($userProviderId) : null) ->replaceArgument(3, $successHandler) ->replaceArgument(4, $failureHandler) ->replaceArgument(5, $config['realm']) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/StatelessAuthenticatorFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/StatelessAuthenticatorFactoryInterface.php new file mode 100644 index 0000000000000..4d536019b36e7 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/StatelessAuthenticatorFactoryInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Stateless authenticators are authenticators that can work without a user provider. + * + * This situation can only occur in stateless firewalls, as statefull firewalls + * need the user provider to refresh the user in each subsequent request. A + * stateless authenticator can be used on both stateless and statefull authenticators. + * + * @author Wouter de Jong + */ +interface StatelessAuthenticatorFactoryInterface extends AuthenticatorFactoryInterface +{ + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string|array; +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 6b91a65d14ae7..9aa0293651b37 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -14,6 +14,7 @@ use Symfony\Bridge\Twig\Extension\LogoutUrlExtension; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\StatelessAuthenticatorFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; use Symfony\Bundle\SecurityBundle\SecurityBundle; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -615,6 +616,10 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri throw new InvalidConfigurationException(sprintf('Authenticator factory "%s" ("%s") must implement "%s".', get_debug_type($factory), $key, AuthenticatorFactoryInterface::class)); } + if (null === $userProvider && !$factory instanceof StatelessAuthenticatorFactoryInterface) { + $userProvider = $this->createMissingUserProvider($container, $id, $key); + } + $authenticators = $factory->createAuthenticator($container, $id, $firewall[$key], $userProvider); if (\is_array($authenticators)) { foreach ($authenticators as $authenticator) { @@ -641,7 +646,7 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri return [$listeners, $defaultEntryPoint]; } - private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): string + private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): ?string { if (isset($firewall[$factoryKey]['provider'])) { if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$factoryKey]['provider'])])) { @@ -660,13 +665,11 @@ private function getUserProvider(ContainerBuilder $container, string $id, array } if (!$providerIds) { - $userProvider = sprintf('security.user.provider.missing.%s', $factoryKey); - $container->setDefinition( - $userProvider, - (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id) - ); + if ($firewall['stateless'] ?? false) { + return null; + } - return $userProvider; + return $this->createMissingUserProvider($container, $id, $factoryKey); } if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey || 'custom_authenticators' === $factoryKey) { @@ -680,6 +683,17 @@ private function getUserProvider(ContainerBuilder $container, string $id, array throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" authenticator on "%s" firewall is ambiguous as there is more than one registered provider.', $factoryKey, $id)); } + private function createMissingUserProvider(ContainerBuilder $container, string $id, string $factoryKey): string + { + $userProvider = sprintf('security.user.provider.missing.%s', $factoryKey); + $container->setDefinition( + $userProvider, + (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id) + ); + + return $userProvider; + } + private function createHashers(array $hashers, ContainerBuilder $container) { $hasherMap = []; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php index 01b205737dd59..d07b5ed3e8eb7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php @@ -315,4 +315,16 @@ public function customQueryAccessTokenFailure(): iterable { yield ['/foo?protection_token=INVALID_ACCESS_TOKEN']; } + + public function testSelfContainedTokens() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_self_contained_token.yml']); + $client->catchExceptions(false); + $client->request('GET', '/foo', [], [], ['HTTP_AUTHORIZATION' => 'Bearer SELF_CONTAINED_ACCESS_TOKEN']); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true)); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php index 4f94cc6936a05..8e6c7b6dd44d0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php @@ -12,19 +12,17 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler; use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; class AccessTokenHandler implements AccessTokenHandlerInterface { - public function __construct() - { - } - public function getUserBadgeFrom(string $accessToken): UserBadge { return match ($accessToken) { 'VALID_ACCESS_TOKEN' => new UserBadge('dunglas'), + 'SELF_CONTAINED_ACCESS_TOKEN' => new UserBadge('dunglas', fn () => new InMemoryUser('dunglas', null, ['ROLE_USER'])), default => throw new BadCredentialsException('Invalid credentials.'), }; } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_self_contained_token.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_self_contained_token.yml new file mode 100644 index 0000000000000..8143698fdec1a --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_self_contained_token.yml @@ -0,0 +1,26 @@ +imports: + - { resource: ./../config/framework.yml } + +framework: + http_method_override: false + serializer: ~ + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + firewalls: + main: + pattern: ^/ + stateless: true + access_token: + token_handler: access_token.access_token_handler + token_extractors: 'header' + realm: 'My API' + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler From b20fce5001d620aa90ed1e2696a8401c734eb357 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sat, 3 Dec 2022 18:55:48 +0100 Subject: [PATCH 069/475] [FrameworkBundle] Improve UX ConfigDebugCommand has not yaml component --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../Command/ConfigDebugCommand.php | 42 +++++++++++++++++-- .../Functional/ConfigDebugCommandTest.php | 16 +++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 1ecd16712ab2f..ff147727d45a3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `DomCrawlerAssertionsTrait::assertSelectorCount(int $count, string $selector)` * Allow to avoid `limit` definition in a RateLimiter configuration when using the `no_limit` policy + * Add `--format` option to the `debug:config` command 6.2 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index c5078d1b5b401..b80bf4d0b0319 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -40,13 +41,17 @@ class ConfigDebugCommand extends AbstractConfigCommand { protected function configure() { + $commentedHelpFormats = array_map(static fn (string $format): string => sprintf('%s', $format), $this->getAvailableFormatOptions()); + $helpFormats = implode('", "', $commentedHelpFormats); + $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), new InputOption('resolve-env', null, InputOption::VALUE_NONE, 'Display resolved environment variable values instead of placeholders'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), class_exists(Yaml::class) ? 'yaml' : 'json'), ]) - ->setHelp(<<<'EOF' + ->setHelp(<<%command.name% command dumps the current configuration for an extension/bundle. @@ -55,6 +60,11 @@ protected function configure() php %command.full_name% framework php %command.full_name% FrameworkBundle +The --format option specifies the format of the configuration, +this is either "{$helpFormats}". + + php %command.full_name% framework --format=json + For dumping a specific option, add its path as second argument: php %command.full_name% framework serializer.enabled @@ -92,12 +102,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int $config = $this->getConfig($extension, $container, $input->getOption('resolve-env')); + $format = $input->getOption('format'); + + if ('yaml' === $format && !class_exists(Yaml::class)) { + $errorIo->error('Setting the "format" option to "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=json" instead.'); + + return 1; + } + if (null === $path = $input->getArgument('path')) { $io->title( sprintf('Current configuration for %s', $name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name)) ); - $io->writeln(Yaml::dump([$extensionAlias => $config], 10)); + $io->writeln($this->convertToFormat([$extensionAlias => $config], $format)); return 0; } @@ -112,11 +130,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->title(sprintf('Current configuration for "%s.%s"', $extensionAlias, $path)); - $io->writeln(Yaml::dump($config, 10)); + $io->writeln($this->convertToFormat($config, $format)); return 0; } + private function convertToFormat(mixed $config, string $format): string + { + return match ($format) { + 'yaml' => Yaml::dump($config, 10), + 'json' => json_encode($config, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + }; + } + private function compileContainer(): ContainerBuilder { $kernel = clone $this->getApplication()->getKernel(); @@ -194,6 +221,10 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } catch (LogicException) { } } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } } private function getAvailableBundles(bool $alias): array @@ -228,4 +259,9 @@ private static function buildPathsCompletion(array $paths, string $prefix = ''): return $completionPaths; } + + private function getAvailableFormatOptions(): array + { + return ['yaml', 'json']; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php index 09b99f82f7c64..ce4764be765ec 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Tester\CommandCompletionTester; @@ -50,6 +51,19 @@ public function testDumpBundleOption() $this->assertStringContainsString('foo', $tester->getDisplay()); } + public function testDumpWithUnsupportedFormat() + { + $tester = $this->createCommandTester(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Supported formats are "yaml", "json"'); + + $tester->execute([ + 'name' => 'test', + '--format' => 'xml', + ]); + } + public function testParametersValuesAreResolved() { $tester = $this->createCommandTester(); @@ -157,6 +171,8 @@ public function provideCompletionSuggestions(): \Generator yield 'name (started CamelCase)' => [['Fra'], ['DefaultConfigTestBundle', 'ExtensionWithoutConfigTestBundle', 'FrameworkBundle', 'TestBundle']]; yield 'name with existing path' => [['framework', ''], ['secret', 'router.resource', 'router.utf8', 'router.enabled', 'validation.enabled', 'default_locale']]; + + yield 'option --format' => [['--format', ''], ['yaml', 'json']]; } private function createCommandTester(): CommandTester From ef6a18297b2eb3b8cf0b7c2bed49b00f282ffb24 Mon Sep 17 00:00:00 2001 From: Monet Emilien Date: Sat, 17 Dec 2022 13:06:52 +0100 Subject: [PATCH 070/475] [WebProfilerBundle] Add a title and img role to svg of the web debug toolbar --- src/Symfony/Bundle/SecurityBundle/CHANGELOG.md | 1 + .../SecurityBundle/Resources/views/Collector/icon.svg | 3 ++- src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md | 6 ++++++ .../Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg | 3 ++- .../WebProfilerBundle/Resources/views/Icon/config.svg | 3 ++- .../WebProfilerBundle/Resources/views/Icon/exception.svg | 3 ++- .../Bundle/WebProfilerBundle/Resources/views/Icon/form.svg | 3 ++- .../WebProfilerBundle/Resources/views/Icon/logger.svg | 3 ++- .../WebProfilerBundle/Resources/views/Icon/memory.svg | 3 ++- .../Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg | 3 ++- .../WebProfilerBundle/Resources/views/Icon/redirect.svg | 3 ++- .../Bundle/WebProfilerBundle/Resources/views/Icon/time.svg | 3 ++- .../Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg | 3 ++- 13 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index ecf3804fa5253..5f0b7d42d7fb1 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Deprecate enabling bundle and not configuring it * Add `_stateless` attribute to the request when firewall is stateless * Add `StatelessAuthenticatorFactoryInterface` for authenticators targeting `stateless` firewalls only and that don't require a user provider + * Modify "icon.svg" to improve accessibility for blind/low vision users 6.2 --- diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg index b11d1a4637476..10cc2434c2f87 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg +++ b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg @@ -1,4 +1,5 @@ - + + Security diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index 7b4a3d7362852..c78fffefd8a15 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +6.3 +--- + + * Add a "role=img" and an explicit title in the .svg file used by the web debug toolbar + to improve accessibility with screen readers for blind users + 6.1 --- diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg index f357a626a0281..be85557836b5e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg @@ -1,4 +1,5 @@ - + + Cache diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg index e28a1c6aa8c15..8f76cf1791bf4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg @@ -1,4 +1,5 @@ - + + Config diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg index a7a79696dfc63..571aed9c56385 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg @@ -1,4 +1,5 @@ - + + Exception diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg index 5a5ce 10000 e29486e0..55dd62b6fb5a0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg @@ -1,4 +1,5 @@ - + + Cache diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg index ee72a2c5f329d..39a3adffd53b6 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg @@ -1,4 +1,5 @@ - + + Logger diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg index e489e1122a819..8e05e6ef6866e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg @@ -1,4 +1,5 @@ - + + Memory diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg index 838deca87a298..4805aed725ba8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg @@ -1,4 +1,5 @@ - + + Menu diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg index ebf9b6479b32d..a23a9710ffd0a 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg @@ -1,4 +1,5 @@ - + + Redirect diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg index 64bdeac785005..3c8c65fbe0acb 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg @@ -1,4 +1,5 @@ - + + Time diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg index 1211c61066b18..027cb2f11f88e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg @@ -1,4 +1,5 @@ - + + Twig From 35890e1ca999cb92d7b20c81bc07cdc2f17b7bb4 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sun, 6 Nov 2022 22:16:38 +0100 Subject: [PATCH 071/475] [Yaml] Add flag to dump numeric key as string --- src/Symfony/Component/Yaml/CHANGELOG.md | 5 + src/Symfony/Component/Yaml/Dumper.php | 4 + src/Symfony/Component/Yaml/Inline.php | 4 + .../Component/Yaml/Tests/DumperTest.php | 90 ++++++++++++++++++ .../Component/Yaml/Tests/InlineTest.php | 92 +++++++++++++++++++ src/Symfony/Component/Yaml/Yaml.php | 1 + 6 files changed, 196 insertions(+) diff --git a/src/Symfony/Component/Yaml/CHANGELOG.md b/src/Symfony/Component/Yaml/CHANGELOG.md index aeb416958928f..2a5c119df0266 100644 --- a/src/Symfony/Component/Yaml/CHANGELOG.md +++ b/src/Symfony/Component/Yaml/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add support to dump int keys as strings by using the `Yaml::DUMP_NUMERIC_KEY_AS_STRING` flag. + 6.2 --- diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php index c5024ff4e32bf..6259b16f9f251 100644 --- a/src/Symfony/Component/Yaml/Dumper.php +++ b/src/Symfony/Component/Yaml/Dumper.php @@ -64,6 +64,10 @@ public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags $output .= "\n"; } + if (\is_int($key) && Yaml::DUMP_NUMERIC_KEY_AS_STRING & $flags) { + $key = (string) $key; + } + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && str_contains($value, "\n") && !str_contains($value, "\r")) { // If the first line starts with a space character, the spec requires a blockIndicationIndicator // http://www.yaml.org/spec/1.2/spec.html#id2793979 diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index b9e1add2c9450..e326fdab67ec5 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -239,6 +239,10 @@ private static function dumpHashArray(array|\ArrayObject|\stdClass $value, int $ { $output = []; foreach ($value as $key => $val) { + if (\is_int($key) && Yaml::DUMP_NUMERIC_KEY_AS_STRING & $flags) { + $key = (string) $key; + } + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); } diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index d6516d7dd9ec3..96d7bccf09cdb 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -804,6 +804,96 @@ public function testDumpNullAsTilde() $this->assertSame('{ foo: ~ }', $this->dumper->dump(['foo' => null], 0, 0, Yaml::DUMP_NULL_AS_TILDE)); } + /** + * @dataProvider getNumericKeyData + */ + public function testDumpInlineNumericKeyAsString(array $input, bool $inline, int $flags, string $expected) + { + $this->assertSame($expected, $this->dumper->dump($input, $inline ? 0 : 4, 0, $flags)); + } + + public function getNumericKeyData() + { + yield 'Int key with flag inline' => [ + [200 => 'foo'], + true, + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '200': foo }", + ]; + + yield 'Int key without flag inline' => [ + [200 => 'foo'], + true, + 0, + '{ 200: foo }', + ]; + + $expected = <<<'YAML' + '200': foo + + YAML; + + yield 'Int key with flag' => [ + [200 => 'foo'], + false, + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + $expected, + ]; + + $expected = <<<'YAML' + 200: foo + + YAML; + + yield 'Int key without flag' => [ + [200 => 'foo'], + false, + 0, + $expected, + ]; + + $expected = <<<'YAML' + - 200 + - foo + + YAML; + + yield 'List array with flag' => [ + [200, 'foo'], + false, + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + $expected, + ]; + + $expected = <<<'YAML' + '200': !number 5 + + YAML; + + yield 'Int tagged value with flag' => [ + [ + 200 => new TaggedValue('number', 5), + ], + false, + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + $expected, + ]; + + $expected = <<<'YAML' + 200: !number 5 + + YAML; + + yield 'Int tagged value without flag' => [ + [ + 200 => new TaggedValue('number', 5), + ], + false, + 0, + $expected, + ]; + } + public function testDumpIdeographicSpaces() { $expected = <<assertSame($expected, Inline::dump($dateTime)); } + /** + * @dataProvider getNumericKeyData + */ + public function testDumpNumericKeyAsString(array|int $input, int $flags, string $expected) + { + $this->assertSame($expected, Inline::dump($input, $flags)); + } + + public function getNumericKeyData() + { + yield 'Int with flag' => [ + 200, + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + '200', + ]; + + yield 'Int key with flag' => [ + [200 => 'foo'], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '200': foo }", + ]; + + yield 'Int value with flag' => [ + [200 => 200], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '200': 200 }", + ]; + + yield 'String key with flag' => [ + ['200' => 'foo'], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '200': foo }", + ]; + + yield 'Mixed with flag' => [ + [42 => 'a', 'b' => 'c', 'd' => 43], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '42': a, b: c, d: 43 }", + ]; + + yield 'Auto-index with flag' => [ + ['a', 'b', 42], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + '[a, b, 42]', + ]; + + yield 'Complex mixed array with flag' => [ + [ + 42 => [ + 'foo' => 43, + 44 => 'bar', + ], + 45 => 'baz', + 46, + ], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '42': { foo: 43, '44': bar }, '45': baz, '46': 46 }", + ]; + + yield 'Int tagged value with flag' => [ + [ + 'count' => new TaggedValue('number', 5), + ], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + '{ count: !number 5 }', + ]; + + yield 'Array tagged value with flag' => [ + [ + 'user' => new TaggedValue('metadata', [ + 'john', + 42, + ]), + ], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + '{ user: !metadata [john, 42] }', + ]; + + $arrayObject = new \ArrayObject(); + $arrayObject['foo'] = 'bar'; + $arrayObject[42] = 'baz'; + $arrayObject['baz'] = 43; + + yield 'Object value with flag' => [ + [ + 'user' => $arrayObject, + ], + Yaml::DUMP_NUMERIC_KEY_AS_STRING | Yaml::DUMP_OBJECT_AS_MAP, + "{ user: { foo: bar, '42': baz, baz: 43 } }", + ]; + } + public function testDumpUnitEnum() { $this->assertSame("!php/const Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum::BAR", Inline::dump(FooUnitEnum::BAR)); diff --git a/src/Symfony/Component/Yaml/Yaml.php b/src/Symfony/Component/Yaml/Yaml.php index 49784216e96e9..e2d2af731083d 100644 --- a/src/Symfony/Component/Yaml/Yaml.php +++ b/src/Symfony/Component/Yaml/Yaml.php @@ -34,6 +34,7 @@ class Yaml public const PARSE_CUSTOM_TAGS = 512; public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; public const DUMP_NULL_AS_TILDE = 2048; + public const DUMP_NUMERIC_KEY_AS_STRING = 4096; /** * Parses a YAML file into a PHP value. From 55c7ce44a8d72c3ce56d4f20f100585986c46153 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 20 Dec 2022 09:36:11 +0100 Subject: [PATCH 072/475] [DependencyInjection] Add support for nesting autowiring-related attributes into `#[Autowire(...)]` --- .../DependencyInjection/CHANGELOG.md | 1 + .../Compiler/AutowirePass.php | 34 +++++++++++++------ .../Tests/Compiler/AutowirePassTest.php | 23 +++++++++++++ .../includes/autowiring_classes_80.php | 16 +++++++++ 4 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 0282cd57f2823..6f0f9c1056b83 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add options `inline_factories` and `inline_class_loader` to `PhpDumper::dump()` * Deprecate `PhpDumper` options `inline_factories_parameter` and `inline_class_loader_parameter` * Add `RemoveBuildParametersPass`, which removes parameters starting with a dot during compilation + * Add support for nesting autowiring-related attributes into `#[Autowire(...)]` 6.2 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index cf0bd9ae244a0..09b0490adf4c8 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -83,6 +83,24 @@ public function process(ContainerBuilder $container) protected function processValue(mixed $value, bool $isRoot = false): mixed { + if ($value instanceof Autowire) { + return $this->processValue($this->container->getParameterBag()->resolveValue($value->value)); + } + + if ($value instanceof TaggedIterator) { + return new TaggedIteratorArgument($value->tag, $value->indexAttribute, $value->defaultIndexMethod, false, $value->defaultPriorityMethod, (array) $value->exclude); + } + + if ($value instanceof TaggedLocator) { + return new ServiceLocatorArgument(new TaggedIteratorArgument($value->tag, $value->indexAttribute, $value->defaultIndexMethod, true, $value->defaultPriorityMethod, (array) $value->exclude)); + } + + if ($value instanceof MapDecorated) { + $definition = $this->container->getDefinition($this->currentId); + + return new Reference($definition->innerServiceId ?? $this->currentId.'.inner', $definition->decorationOnInvalid ?? ContainerInterface::NULL_ON_INVALID_REFERENCE); + } + try { return $this->doProcessValue($value, $isRoot); } catch (AutowiringFailedException $e) { @@ -170,20 +188,14 @@ private function processAttribute(object $attribute, bool $isOptional = false): { switch (true) { case $attribute instanceof Autowire: - $value = $this->container->getParameterBag()->resolveValue($attribute->value); - - return $value instanceof Reference && $isOptional ? new Reference($value, ContainerInterface::NULL_ON_INVALID_REFERENCE) : $value; - + if ($isOptional && $attribute->value instanceof Reference) { + return new Reference($attribute->value, ContainerInterface::NULL_ON_INVALID_REFERENCE); + } + // no break case $attribute instanceof TaggedIterator: - return new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, $attribute->defaultIndexMethod, false, $attribute->defaultPriorityMethod, (array) $attribute->exclude); - case $attribute instanceof TaggedLocator: - return new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, $attribute->defaultIndexMethod, true, $attribute->defaultPriorityMethod, (array) $attribute->exclude)); - case $attribute instanceof MapDecorated: - $definition = $this->container->getDefinition($this->currentId); - - return new Reference($definition->innerServiceId ?? $this->currentId.'.inner', $definition->decorationOnInvalid ?? ContainerInterface::NULL_ON_INVALID_REFERENCE); + return $this->processValue($attribute); } throw new AutowiringFailedException($this->currentId, sprintf('"%s" is an unsupported attribute.', $attribute::class)); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 958c01ebec4ff..c766bed6aefa4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -15,6 +15,8 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Compiler\AutowireAsDecoratorPass; use Symfony\Component\DependencyInjection\Compiler\AutowirePass; use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass; @@ -1254,4 +1256,25 @@ public function testTypeNamespaceExcluded() $this->assertSame('Cannot autowire service "a": argument "$k" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotGuessableArgument::__construct()" needs an instance of "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" but this type has been excluded from autowiring.', (string) $e->getMessage()); } } + + public function testNestedAttributes() + { + $container = new ContainerBuilder(); + + $container->register(AsDecoratorFoo::class); + $container->register(AutowireNestedAttributes::class)->setAutowired(true); + + (new ResolveClassPass())->process($container); + (new AutowireAsDecoratorPass())->process($container); + (new DecoratorServicePass())->process($container); + (new AutowirePass())->process($container); + + $expected = [ + 'decorated' => new Reference(AutowireNestedAttributes::class.'.inner'), + 'iterator' => new TaggedIteratorArgument('foo'), + 'locator' => new ServiceLocatorArgument(new TaggedIteratorArgument('foo', needsIndexes: true)), + 'service' => new Reference('bar'), + ]; + $this->assertEquals($expected, $container->getDefinition(AutowireNestedAttributes::class)->getArgument(0)); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php index c1c772b684a48..dfbaba206bf42 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php @@ -5,6 +5,8 @@ use Symfony\Component\DependencyInjection\Attribute\AsDecorator; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\MapDecorated; +use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; +use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Contracts\Service\Attribute\Required; @@ -85,3 +87,17 @@ public function __construct(#[MapDecorated] AsDecoratorInterface $inner = null) { } } + +#[AsDecorator(decorates: AsDecoratorFoo::class)] +class AutowireNestedAttributes implements AsDecoratorInterface +{ + public function __construct( + #[Autowire([ + 'decorated' => new MapDecorated(), + 'iterator' => new TaggedIterator('foo'), + 'locator' => new TaggedLocator('foo'), + 'service' => new Autowire(service: 'bar') + ])] array $options) + { + } +} From acae5cada21b4c0505a9d49d7ed4d99804831a2f Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 5 Nov 2022 18:20:29 +0100 Subject: [PATCH 073/475] [Messenger] Do not return fallback senders when other senders were already found --- .../Transport/Sender/SendersLocatorTest.php | 17 +++++++++++++++++ .../Transport/Sender/SendersLocator.php | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php index d58a4d63d50aa..3faab44d0696a 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php @@ -52,6 +52,23 @@ public function testItReturnsTheSenderBasedOnTransportNamesStamp() $this->assertSame([], iterator_to_array($locator->getSenders(new Envelope(new SecondMessage())))); } + public function testSendersMapWithFallback() + { + $firstSender = $this->createMock(SenderInterface::class); + $secondSender = $this->createMock(SenderInterface::class); + $sendersLocator = $this->createContainer([ + 'first' => $firstSender, + 'second' => $secondSender, + ]); + $locator = new SendersLocator([ + DummyMessage::class => ['first'], + '*' => ['second'], + ], $sendersLocator); + + $this->assertSame(['first' => $firstSender], iterator_to_array($locator->getSenders(new Envelope(new DummyMessage('a')))), 'Unexpected senders for configured message'); + $this->assertSame(['second' => $secondSender], iterator_to_array($locator->getSenders(new Envelope(new SecondMessage()))), 'Unexpected senders for unconfigured message'); + } + private function createContainer(array $senders): ContainerInterface { $container = $this->createMock(ContainerInterface::class); diff --git a/src/Symfony/Component/Messenger/Transport/Sender/SendersLocator.php b/src/Symfony/Component/Messenger/Transport/Sender/SendersLocator.php index 5de6936d591f7..3420a6cc6043a 100644 --- a/src/Symfony/Component/Messenger/Transport/Sender/SendersLocator.php +++ b/src/Symfony/Component/Messenger/Transport/Sender/SendersLocator.php @@ -51,6 +51,12 @@ public function getSenders(Envelope $envelope): iterable foreach (HandlersLocator::listTypes($envelope) as $type) { foreach ($this->sendersMap[$type] ?? [] as $senderAlias) { + if (str_ends_with($type, '*') && $seen) { + // the '*' acts as a fallback, if other senders already matched + // with previous types, skip the senders bound to the fallback + continue; + } + if (!\in_array($senderAlias, $seen, true)) { $seen[] = $senderAlias; From d1cbcca6a5f9f1f8c4ba814abc54d0509f828b36 Mon Sep 17 00:00:00 2001 From: Dejan Angelov Date: Sun, 27 Nov 2022 19:26:53 +0100 Subject: [PATCH 074/475] [HttpKernel] Allow using `#[HttpStatus]` for setting status code and headers for HTTP exceptions --- .../HttpKernel/Attribute/HttpStatus.php | 28 ++++++++ src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../EventListener/ErrorListener.php | 15 +++++ .../Tests/EventListener/ErrorListenerTest.php | 66 +++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 src/Symfony/Component/HttpKernel/Attribute/HttpStatus.php diff --git a/src/Symfony/Component/HttpKernel/Attribute/HttpStatus.php b/src/Symfony/Component/HttpKernel/Attribute/HttpStatus.php new file mode 100644 index 0000000000000..a2811150e07e7 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Attribute/HttpStatus.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +/** + * @author Dejan Angelov + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class HttpStatus +{ + /** + * @param array $headers + */ + public function __construct( + public readonly int $statusCode, + public readonly array $headers = [], + ) { + } +} diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index f409a6f13c06c..02c9da3d12b0e 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead * `FileProfilerStorage` removes profiles automatically after two days + * Add `#[HttpStatus]` for defining status codes for exceptions 6.2 --- diff --git a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php index dd6105052028a..11a060903addb 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php @@ -15,6 +15,7 @@ use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\HttpStatus; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; @@ -72,6 +73,20 @@ public function logKernelException(ExceptionEvent $event) break; } + // There's no specific status code defined in the configuration for this exception + if (!$throwable instanceof HttpExceptionInterface) { + $class = new \ReflectionClass($throwable); + $attributes = $class->getAttributes(HttpStatus::class, \ReflectionAttribute::IS_INSTANCEOF); + + if ($attributes) { + /** @var HttpStatus $instance */ + $instance = $attributes[0]->newInstance(); + + $throwable = new HttpException($instance->statusCode, $throwable->getMessage(), $throwable, $instance->headers); + $event->setThrowable($throwable); + } + } + $e = FlattenException::createFromThrowable($throwable); $this->logException($throwable, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), $e->getFile(), $e->getLine()), $logLevel); diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php index e988fe6914956..5090eb11cbb79 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php @@ -17,6 +17,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\HttpStatus; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ExceptionEvent; @@ -117,6 +118,36 @@ public function testHandleWithLoggerAndCustomConfiguration() $this->assertCount(1, $logger->getLogs('warning')); } + /** + * @dataProvider exceptionWithAttributeProvider + */ + public function testHandleHttpAttribute(\Throwable $exception, int $expectedStatusCode, array $expectedHeaders) + { + $request = new Request(); + $event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, $exception); + $l = new ErrorListener('not used'); + $l->logKernelException($event); + $l->onKernelException($event); + + $this->assertEquals(new Response('foo', $expectedStatusCode, $expectedHeaders), $event->getResponse()); + } + + public function testHandleCustomConfigurationAndHttpAttribute() + { + $request = new Request(); + $event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new WithGeneralAttribute()); + $l = new ErrorListener('not used', null, false, [ + WithGeneralAttribute::class => [ + 'log_level' => 'warning', + 'status_code' => 401, + ], + ]); + $l->logKernelException($event); + $l->onKernelException($event); + + $this->assertEquals(new Response('foo', 401), $event->getResponse()); + } + public function provider() { if (!class_exists(Request::class)) { @@ -216,6 +247,12 @@ public function controllerProvider() return new Response('OK: '.$exception->getMessage()); }]; } + + public static function exceptionWithAttributeProvider() + { + yield [new WithCustomUserProvidedAttribute(), 208, ['name' => 'value']]; + yield [new WithGeneralAttribute(), 412, ['some' => 'thing']]; + } } class TestLogger extends Logger implements DebugLoggerInterface @@ -246,3 +283,32 @@ public function handle(Request $request, $type = self::MAIN_REQUEST, $catch = tr throw new \RuntimeException('bar'); } } + +#[\Attribute(\Attribute::TARGET_CLASS)] +class UserProvidedHttpStatusCodeAttribute extends HttpStatus +{ + public function __construct(array $headers = []) + { + parent::__construct( + Response::HTTP_ALREADY_REPORTED, + $headers + ); + } +} + +#[UserProvidedHttpStatusCodeAttribute(headers: [ + 'name' => 'value', +])] +class WithCustomUserProvidedAttribute extends \Exception +{ +} + +#[HttpStatus( + statusCode: Response::HTTP_PRECONDITION_FAILED, + headers: [ + 'some' => 'thing', + ] +)] +class WithGeneralAttribute extends \Exception +{ +} From 54b3008a7f2000e97789a1a7106e5b3c0314f7cb Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 21 Dec 2022 19:25:36 +0100 Subject: [PATCH 075/475] Add generics to PasswordUpgraderInterface --- .../Security/Core/User/PasswordUpgraderInterface.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Security/Core/User/PasswordUpgraderInterface.php b/src/Symfony/Component/Security/Core/User/PasswordUpgraderInterface.php index fd21f14a81285..6bf18c7fbccdc 100644 --- a/src/Symfony/Component/Security/Core/User/PasswordUpgraderInterface.php +++ b/src/Symfony/Component/Security/Core/User/PasswordUpgraderInterface.php @@ -13,6 +13,8 @@ /** * @author Nicolas Grekas + * + * @template TUser of PasswordAuthenticatedUserInterface */ interface PasswordUpgraderInterface { @@ -22,6 +24,8 @@ interface PasswordUpgraderInterface * This method should persist the new password in the user storage and update the $user object accordingly. * Because you don't want your users not being able to log in, this method should be opportunistic: * it's fine if it does nothing or if it fails without throwing any exception. + * + * @param TUser $user */ public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void; } From 568191f284ab84e98b44e464901fefd8dcc15ba7 Mon Sep 17 00:00:00 2001 From: Nicolas Sauveur Date: Fri, 9 Dec 2022 15:27:41 -0100 Subject: [PATCH 076/475] [Security] Make login redirection logic available to programmatic login --- .../Bundle/SecurityBundle/CHANGELOG.md | 1 + .../Bundle/SecurityBundle/Security.php | 7 ++- .../SecurityBundle/Tests/SecurityTest.php | 51 +++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 5f0b7d42d7fb1..4ab9103275bb4 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Add `_stateless` attribute to the request when firewall is stateless * Add `StatelessAuthenticatorFactoryInterface` for authenticators targeting `stateless` firewalls only and that don't require a user provider * Modify "icon.svg" to improve accessibility for blind/low vision users + * Make `Security::login()` return the authenticator response 6.2 --- diff --git a/src/Symfony/Bundle/SecurityBundle/Security.php b/src/Symfony/Bundle/SecurityBundle/Security.php index d78cfc0edd02f..1ef5a50008c9c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security.php +++ b/src/Symfony/Bundle/SecurityBundle/Security.php @@ -56,8 +56,10 @@ public function getFirewallConfig(Request $request): ?FirewallConfig * @param UserInterface $user The user to authenticate * @param string|null $authenticatorName The authenticator name (e.g. "form_login") or service id (e.g. SomeApiKeyAuthenticator::class) - required only if multiple authenticators are configured * @param string|null $firewallName The firewall name - required only if multiple firewalls are configured + * + * @return Response|null The authenticator success response if any */ - public function login(UserInterface $user, string $authenticatorName = null, string $firewallName = null): void + public function login(UserInterface $user, string $authenticatorName = null, string $firewallName = null): ?Response { $request = $this->container->get('request_stack')->getCurrentRequest(); $firewallName ??= $this->getFirewallConfig($request)?->getName(); @@ -69,7 +71,8 @@ public function login(UserInterface $user, string $authenticatorName = null, str $authenticator = $this->getAuthenticator($authenticatorName, $firewallName); $this->container->get('security.user_checker')->checkPreAuth($user); - $this->container->get('security.user_authenticator')->authenticateUser($user, $authenticator, $request); + + return $this->container->get('security.user_authenticator')->authenticateUser($user, $authenticator, $request); } /** diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php index b69467cb95cf3..9b5fcf3b51ddf 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php @@ -169,6 +169,57 @@ public function testLogin() $security->login($user); } + public function testLoginReturnsAuthenticatorResponse() + { + $request = new Request(); + $authenticator = $this->createMock(AuthenticatorInterface::class); + $requestStack = $this->createMock(RequestStack::class); + $firewallMap = $this->createMock(FirewallMap::class); + $firewall = new FirewallConfig('main', 'main'); + $user = $this->createMock(UserInterface::class); + $userChecker = $this->createMock(UserCheckerInterface::class); + $userAuthenticator = $this->createMock(UserAuthenticatorInterface::class); + + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['request_stack', $requestStack], + ['security.firewall.map', $firewallMap], + ['security.user_authenticator', $userAuthenticator], + ['security.user_checker', $userChecker], + ]) + ; + + $requestStack->expects($this->once())->method('getCurrentRequest')->willReturn($request); + $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewall); + $userChecker->expects($this->once())->method('checkPreAuth')->with($user); + $userAuthenticator->expects($this->once())->method('authenticateUser') + ->with($user, $authenticator, $request) + ->willReturn(new Response('authenticator response')); + + $firewallAuthenticatorLocator = $this->createMock(ServiceProviderInterface::class); + $firewallAuthenticatorLocator + ->expects($this->once()) + ->method('getProvidedServices') + ->willReturn(['security.authenticator.custom.dev' => $authenticator]) + ; + $firewallAuthenticatorLocator + ->expects($this->once()) + ->method('get') + ->with('security.authenticator.custom.dev') + ->willReturn($authenticator) + ; + + $security = new Security($container, ['main' => $firewallAuthenticatorLocator]); + + $response = $security->login($user); + + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals('authenticator response', $response->getContent()); + } + public function testLoginWithoutAuthenticatorThrows() { $request = new Request(); From c352f5323463b379757e3ecccb758fe1e41b5292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Brzuchalski?= Date: Wed, 7 Dec 2022 11:12:31 +0100 Subject: [PATCH 077/475] [FrameworkBundle][Messenger] Add support for namespace wildcard in Messenger routing --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../FrameworkExtension.php | 6 ++++- .../Fixtures/php/messenger_routing.php | 1 + .../messenger_routing_invalid_wildcard.php | 17 ++++++++++++++ .../Fixtures/xml/messenger_routing.xml | 3 +++ .../messenger_routing_invalid_wildcard.xml | 18 +++++++++++++++ .../Fixtures/yml/messenger_routing.yml | 2 ++ .../messenger_routing_invalid_wildcard.yml | 10 ++++++++ .../FrameworkExtensionTest.php | 7 ++++++ src/Symfony/Component/Messenger/CHANGELOG.md | 1 + .../Messenger/Handler/HandlersLocator.php | 13 +++++++++++ .../Tests/Handler/HandlersLocatorTest.php | 23 +++++++++++++++++++ 12 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_wildcard.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing_invalid_wildcard.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing_invalid_wildcard.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index ff147727d45a3..2967592e00109 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add `DomCrawlerAssertionsTrait::assertSelectorCount(int $count, string $selector)` * Allow to avoid `limit` definition in a RateLimiter configuration when using the `no_limit` policy * Add `--format` option to the `debug:config` command + * Add support to pass namespace wildcard in `framework.messenger.routing` 6.2 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index a75bd61f0511c..2e43e6a2fb00d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2159,7 +2159,11 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $messageToSendersMapping = []; foreach ($config['routing'] as $message => $messageConfiguration) { - if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) { + if ('*' !== $message && !class_exists($message) && !interface_exists($message, false) && !preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++\*$/', $message)) { + if (str_contains($message, '*')) { + throw new LogicException(sprintf('Invalid Messenger routing configuration: invalid namespace "%s" wildcard.', $message)); + } + throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message)); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php index c045cace2589a..ef1d09e893194 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php @@ -15,6 +15,7 @@ SecondMessage::class => [ 'senders' => ['amqp', 'audit'], ], + 'Symfony\*' => 'amqp', '*' => 'amqp', ], 'transports' => [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_wildcard.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_wildcard.php new file mode 100644 index 0000000000000..c8e046ef64ca4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_wildcard.php @@ -0,0 +1,17 @@ +loadFromExtension('framework', [ + 'http_method_override' => false, + 'serializer' => true, + 'messenger' => [ + 'serializer' => [ + 'default_serializer' => 'messenger.transport.symfony_serializer', + ], + 'routing' => [ + 'Symfony\*\DummyMessage' => ['audit'], + ], + 'transports' => [ + 'audit' => 'null://', + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml index 30b249b415c31..960939ad3916f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml @@ -20,6 +20,9 @@ + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing_invalid_wildcard.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing_invalid_wildcard.xml new file mode 100644 index 0000000000000..d6a07d6bad74e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing_invalid_wildcard.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml index bfd682c706fbe..c064c8c5ba2b7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml @@ -8,6 +8,8 @@ framework: 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage': [amqp, messenger.transport.audit] 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage': senders: [amqp, audit] + 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\*': [amqp] + 'Symfony\*': [amqp] '*': amqp transports: amqp: 'amqp://localhost/%2f/messages' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing_invalid_wildcard.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing_invalid_wildcard.yml new file mode 100644 index 0000000000000..af5a0d22218a7 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing_invalid_wildcard.yml @@ -0,0 +1,10 @@ +framework: + http_method_override: false + serializer: true + messenger: + serializer: + default_serializer: messenger.transport.symfony_serializer + routing: + 'Symfony\*\DummyMessage': [audit] + transports: + audit: 'null://' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 90228e9052723..d3c24f28dcadc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1059,6 +1059,13 @@ public function testMessengerMiddlewareFactoryErroneousFormat() } public function testMessengerInvalidTransportRouting() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Invalid Messenger routing configuration: invalid namespace "Symfony\*\DummyMessage" wildcard.'); + $this->createContainerFromFile('messenger_routing_invalid_wildcard'); + } + + public function testMessengerInvalidWildcardRouting() { $this->expectException(\LogicException::class); $this->expectExceptionMessage('Invalid Messenger routing configuration: the "Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage" class is being routed to a sender called "invalid". This is not a valid transport or service id.'); diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index 4e2f52ebf2ff3..e0aa2610d33d2 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.3 --- + * Add support for namespace wildcards in the HandlersLocator to allow routing multiple messages within the same namespace * Deprecate `Symfony\Component\Messenger\Transport\InMemoryTransport` and `Symfony\Component\Messenger\Transport\InMemoryTransportFactory` in favor of `Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport` and diff --git a/src/Symfony/Component/Messenger/Handler/HandlersLocator.php b/src/Symfony/Component/Messenger/Handler/HandlersLocator.php index daeacdfa82dcf..6c5daf3a718af 100644 --- a/src/Symfony/Component/Messenger/Handler/HandlersLocator.php +++ b/src/Symfony/Component/Messenger/Handler/HandlersLocator.php @@ -68,9 +68,22 @@ public static function listTypes(Envelope $envelope): array return [$class => $class] + class_parents($class) + class_implements($class) + + self::listWildcards($class) + ['*' => '*']; } + private static function listWildcards(string $type): array + { + $type .= '\*'; + $wildcards = []; + while ($i = strrpos($type, '\\', -3)) { + $type = substr_replace($type, '\*', $i); + $wildcards[$type] = $type; + } + + return $wildcards; + } + private function shouldHandle(Envelope $envelope, HandlerDescriptor $handlerDescriptor): bool { if (null === $received = $envelope->last(ReceivedStamp::class)) { diff --git a/src/Symfony/Component/Messenger/Tests/Handler/HandlersLocatorTest.php b/src/Symfony/Component/Messenger/Tests/Handler/HandlersLocatorTest.php index 7bb7347877b6c..fe6782672e2d0 100644 --- a/src/Symfony/Component/Messenger/Tests/Handler/HandlersLocatorTest.php +++ b/src/Symfony/Component/Messenger/Tests/Handler/HandlersLocatorTest.php @@ -56,6 +56,29 @@ public function testItReturnsOnlyHandlersMatchingTransport() new Envelope(new DummyMessage('Body'), [new ReceivedStamp('transportName')]) ))); } + + public function testItReturnsOnlyHandlersMatchingMessageNamespace() + { + $firstHandler = $this->createPartialMock(HandlersLocatorTestCallable::class, ['__invoke']); + $secondHandler = $this->createPartialMock(HandlersLocatorTestCallable::class, ['__invoke']); + + $locator = new HandlersLocator([ + str_replace('DummyMessage', '*', DummyMessage::class) => [ + $first = new HandlerDescriptor($firstHandler, ['alias' => 'one']), + ], + str_replace('Fixtures\\DummyMessage', '*', DummyMessage::class) => [ + $second = new HandlerDescriptor($secondHandler, ['alias' => 'two']), + ], + ]); + + $first->getName(); + $second->getName(); + + $this->assertEquals([ + $first, + $second, + ], iterator_to_array($locator->getHandlers(new Envelope(new DummyMessage('Body'))))); + } } class HandlersLocatorTestCallable From aaad65cc9321aaf4b20da597b16fadff4b1ce1b4 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 14 Dec 2022 13:28:32 +0100 Subject: [PATCH 078/475] [Clock] Add `Clock` class and `now()` function --- composer.json | 1 + .../Resources/config/services.php | 4 +- .../Bundle/FrameworkBundle/composer.json | 1 + src/Symfony/Component/Clock/CHANGELOG.md | 1 + src/Symfony/Component/Clock/Clock.php | 72 ++++++++++++++++ src/Symfony/Component/Clock/MockClock.php | 2 + src/Symfony/Component/Clock/Resources/now.php | 23 ++++++ .../Clock/Test/ClockSensitiveTrait.php | 65 +++++++++++++++ .../Component/Clock/Tests/ClockTest.php | 82 +++++++++++++++++++ src/Symfony/Component/Clock/composer.json | 1 + 10 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Clock/Clock.php create mode 100644 src/Symfony/Component/Clock/Resources/now.php create mode 100644 src/Symfony/Component/Clock/Test/ClockSensitiveTrait.php create mode 100644 src/Symfony/Component/Clock/Tests/ClockTest.php diff --git a/composer.json b/composer.json index 7503cccb8e26c..919136a0a2438 100644 --- a/composer.json +++ b/composer.json @@ -187,6 +187,7 @@ }, "autoload-dev": { "files": [ + "src/Symfony/Component/Clock/Resources/now.php", "src/Symfony/Component/VarDumper/Resources/functions/dump.php" ] }, diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php index 9facc09c783f8..6d805bfbc7dd1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php @@ -15,8 +15,8 @@ use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; use Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer; use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; +use Symfony\Component\Clock\Clock; use Symfony\Component\Clock\ClockInterface; -use Symfony\Component\Clock\NativeClock; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; use Symfony\Component\Config\ResourceCheckerConfigCacheFactory; @@ -229,7 +229,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] ->args([service(KernelInterface::class), service('logger')->nullOnInvalid()]) ->tag('kernel.cache_warmer') - ->set('clock', NativeClock::class) + ->set('clock', Clock::class) ->alias(ClockInterface::class, 'clock') ->alias(PsrClockInterface::class, 'clock') diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 509d4278daca6..b9531d60f972f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -76,6 +76,7 @@ "phpdocumentor/type-resolver": "<1.4.0", "phpunit/phpunit": "<5.4.3", "symfony/asset": "<5.4", + "symfony/clock": "<6.3", "symfony/console": "<5.4", "symfony/dotenv": "<5.4", "symfony/dom-crawler": "<5.4", diff --git a/src/Symfony/Component/Clock/CHANGELOG.md b/src/Symfony/Component/Clock/CHANGELOG.md index 9bb1c2d4148e1..e59500a9efea0 100644 --- a/src/Symfony/Component/Clock/CHANGELOG.md +++ b/src/Symfony/Component/Clock/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `ClockAwareTrait` to help write time-sensitive classes + * Add `Clock` class and `now()` function 6.2 --- diff --git a/src/Symfony/Component/Clock/Clock.php b/src/Symfony/Component/Clock/Clock.php new file mode 100644 index 0000000000000..8d72b8de99f1b --- /dev/null +++ b/src/Symfony/Component/Clock/Clock.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +use Psr\Clock\ClockInterface as PsrClockInterface; + +/** + * A global clock. + * + * @author Nicolas Grekas + */ +final class Clock implements ClockInterface +{ + private static ClockInterface $globalClock; + + public function __construct( + private readonly ?PsrClockInterface $clock = null, + private ?\DateTimeZone $timezone = null, + ) { + } + + /** + * Returns the current global clock. + * + * Note that you should prefer injecting a ClockInterface or using + * ClockAwareTrait when possible instead of using this method. + */ + public static function get(): ClockInterface + { + return self::$globalClock ??= new NativeClock(); + } + + public static function set(PsrClockInterface $clock): void + { + self::$globalClock = $clock instanceof ClockInterface ? $clock : new self($clock); + } + + public function now(): \DateTimeImmutable + { + $now = ($this->clock ?? self::$globalClock)->now(); + + return isset($this->timezone) ? $now->setTimezone($this->timezone) : $now; + } + + public function sleep(float|int $seconds): void + { + $clock = $this->clock ?? self::$globalClock; + + if ($clock instanceof ClockInterface) { + $clock->sleep($seconds); + } else { + (new NativeClock())->sleep($seconds); + } + } + + public function withTimeZone(\DateTimeZone|string $timezone): static + { + $clone = clone $this; + $clone->timezone = \is_string($timezone) ? new \DateTimeZone($timezone) : $timezone; + + return $clone; + } +} diff --git a/src/Symfony/Component/Clock/MockClock.php b/src/Symfony/Component/Clock/MockClock.php index a189ff54c4dc9..5a3a207217ba9 100644 --- a/src/Symfony/Component/Clock/MockClock.php +++ b/src/Symfony/Component/Clock/MockClock.php @@ -14,6 +14,8 @@ /** * A clock that always returns the same date, suitable for testing time-sensitive logic. * + * Consider using ClockSensitiveTrait in your test cases instead of using this class directly. + * * @author Nicolas Grekas */ final class MockClock implements ClockInterface diff --git a/src/Symfony/Component/Clock/Resources/now.php b/src/Symfony/Component/Clock/Resources/now.php new file mode 100644 index 0000000000000..7da0142b1c8bc --- /dev/null +++ b/src/Symfony/Component/Clock/Resources/now.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +/** + * Returns the current time as a DateTimeImmutable. + * + * Note that you should prefer injecting a ClockInterface or using + * ClockAwareTrait when possible instead of using this function. + */ +function now(): \DateTimeImmutable +{ + return Clock::get()->now(); +} diff --git a/src/Symfony/Component/Clock/Test/ClockSensitiveTrait.php b/src/Symfony/Component/Clock/Test/ClockSensitiveTrait.php new file mode 100644 index 0000000000000..68c69e398acd5 --- /dev/null +++ b/src/Symfony/Component/Clock/Test/ClockSensitiveTrait.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock\Test; + +use Psr\Clock\ClockInterface; +use Symfony\Component\Clock\Clock; +use Symfony\Component\Clock\MockClock; + +use function Symfony\Component\Clock\now; + +/** + * Helps with mocking the time in your test cases. + * + * This trait provides one self::mockTime() method that freezes the time. + * It restores the global clock after each test case. + * self::mockTime() accepts either a string (eg '+1 days' or '2022-12-22'), + * a DateTimeImmutable, or a boolean (to freeze/restore the global clock). + * + * @author Nicolas Grekas + */ +trait ClockSensitiveTrait +{ + public static function mockTime(string|\DateTimeImmutable|bool $when = true): ClockInterface + { + Clock::set(match (true) { + false === $when => self::saveClockBeforeTest(false), + true === $when => new MockClock(), + $when instanceof \DateTimeImmutable => new MockClock($when), + default => new MockClock(now()->modify($when)), + }); + + return Clock::get(); + } + + /** + * @before + * + * @internal + */ + protected static function saveClockBeforeTest(bool $save = true): ClockInterface + { + static $originalClock; + + return $save ? $originalClock = Clock::get() : $originalClock; + } + + /** + * @after + * + * @internal + */ + protected static function restoreClockAfterTest(): void + { + Clock::set(self::saveClockBeforeTest(false)); + } +} diff --git a/src/Symfony/Component/Clock/Tests/ClockTest.php b/src/Symfony/Component/Clock/Tests/ClockTest.php new file mode 100644 index 0000000000000..e88ade2558777 --- /dev/null +++ b/src/Symfony/Component/Clock/Tests/ClockTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock\Tests; + +use PHPUnit\Framework\TestCase; +use Psr\Clock\ClockInterface; +use Symfony\Component\Clock\Clock; +use Symfony\Component\Clock\MockClock; +use Symfony\Component\Clock\NativeClock; +use Symfony\Component\Clock\Test\ClockSensitiveTrait; + +use function Symfony\Component\Clock\now; + +class ClockTest extends TestCase +{ + use ClockSensitiveTrait; + + public function testMockClock() + { + $this->assertInstanceOf(NativeClock::class, Clock::get()); + + $clock = self::mockTime(); + $this->assertInstanceOf(MockClock::class, Clock::get()); + $this->assertSame(Clock::get(), $clock); + } + + public function testNativeClock() + { + $this->assertInstanceOf(\DateTimeImmutable::class, now()); + $this->assertInstanceOf(NativeClock::class, Clock::get()) 10000 ; + } + + public function testMockClockDisable() + { + $this->assertInstanceOf(NativeClock::class, Clock::get()); + + $this->assertInstanceOf(MockClock::class, self::mockTime(true)); + $this->assertInstanceOf(NativeClock::class, self::mockTime(false)); + } + + public function testMockClockFreeze() + { + self::mockTime(new \DateTimeImmutable('2021-12-19')); + + $this->assertSame('2021-12-19', now()->format('Y-m-d')); + + self::mockTime('+1 days'); + $this->assertSame('2021-12-20', now()->format('Y-m-d')); + } + + public function testPsrClock() + { + $psrClock = new class() implements ClockInterface { + public function now(): \DateTimeImmutable + { + return new \DateTimeImmutable('@1234567'); + } + }; + + Clock::set($psrClock); + + $this->assertInstanceOf(Clock::class, Clock::get()); + + $this->assertSame(1234567, now()->getTimestamp()); + + $this->assertSame('UTC', Clock::get()->withTimeZone('UTC')->now()->getTimezone()->getName()); + $this->assertSame('Europe/Paris', Clock::get()->withTimeZone('Europe/Paris')->now()->getTimezone()->getName()); + + Clock::get()->sleep(0.1); + + $this->assertSame(1234567, now()->getTimestamp()); + } +} diff --git a/src/Symfony/Component/Clock/composer.json b/src/Symfony/Component/Clock/composer.json index 2ff68a82227fb..2c796b0fda9cf 100644 --- a/src/Symfony/Component/Clock/composer.json +++ b/src/Symfony/Component/Clock/composer.json @@ -23,6 +23,7 @@ "psr/clock": "^1.0" }, "autoload": { + "files": [ "Resources/now.php" ], "psr-4": { "Symfony\\Component\\Clock\\": "" }, "exclude-from-classmap": [ "/Tests/" From c0e9ee16eba3913ce90842a7e7356d0107283251 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 20 Dec 2022 20:08:50 +0100 Subject: [PATCH 079/475] Remove calls to AnnotationRegistry::registerLoader() --- composer.json | 2 +- .../Tests/PropertyInfo/DoctrineExtractorTest.php | 5 +---- src/Symfony/Bridge/Doctrine/composer.json | 7 ++++--- .../PhpUnit/Legacy/SymfonyTestsListenerTrait.php | 9 --------- src/Symfony/Bridge/PhpUnit/bootstrap.php | 9 --------- src/Symfony/Bridge/PhpUnit/composer.json | 1 + .../DependencyInjection/FrameworkExtension.php | 10 ---------- .../FrameworkBundle/Resources/config/annotations.php | 9 +-------- .../AnnotationClassLoaderWithAnnotationsTest.php | 4 ---- 9 files changed, 8 insertions(+), 48 deletions(-) diff --git a/composer.json b/composer.json index 0d08ed1443508..5a8056a82067c 100644 --- a/composer.json +++ b/composer.json @@ -128,7 +128,7 @@ "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.7.4", + "doctrine/orm": "^2.12", "egulias/email-validator": "^2.1.10|^3.1", "guzzlehttp/promises": "^1.4", "league/html-to-markdown": "^5.0", diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index 3771e772afc49..f0740610e4b6d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -16,7 +16,6 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\ORMSetup; -use Doctrine\ORM\Tools\Setup; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy; @@ -34,9 +33,7 @@ class DoctrineExtractorTest extends TestCase { private function createExtractor() { - $config = class_exists(ORMSetup::class) - ? ORMSetup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true) - : Setup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true); + $config = ORMSetup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true); $entityManager = EntityManager::create(['driver' => 'pdo_sqlite'], $config); if (!DBALType::hasType('foo')) { diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 49931c02bac5a..bee7d29c5b1b6 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -42,17 +42,18 @@ "symfony/validator": "^5.4|^6.0", "symfony/translation": "^5.4|^6.0", "symfony/var-dumper": "^5.4|^6.0", - "doctrine/annotations": "^1.10.4|^2", + "doctrine/annotations": "^1.13.1|^2", "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.7.4", + "doctrine/orm": "^2.12", "psr/log": "^1|^2|^3" }, "conflict": { + "doctrine/annotations": "<1.13.1", "doctrine/dbal": "<2.13.1", "doctrine/lexer": "<1.1", - "doctrine/orm": "<2.7.4", + "doctrine/orm": "<2.12", "phpunit/phpunit": "<5.4.3", "symfony/cache": "<5.4", "symfony/dependency-injection": "<6.2", diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index 015bc881ae573..b97a11d614929 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\PhpUnit\Legacy; -use Doctrine\Common\Annotations\AnnotationRegistry; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\RiskyTestError; use PHPUnit\Framework\TestCase; @@ -130,14 +129,6 @@ public function startTestSuite($suite) echo "Testing $suiteName\n"; $this->state = 0; - if (!class_exists(AnnotationRegistry::class, false) && class_exists(AnnotationRegistry::class)) { - if (method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { - AnnotationRegistry::registerUniqueLoader('class_exists'); - } elseif (method_exists(AnnotationRegistry::class, 'registerLoader')) { - AnnotationRegistry::registerLoader('class_exists'); - } - } - if ($this->skippedFile = getenv('SYMFONY_PHPUNIT_SKIPPED_TESTS')) { $this->state = 1; diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php index f2064368f41a3..c81c828770dbc 100644 --- a/src/Symfony/Bridge/PhpUnit/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php @@ -9,7 +9,6 @@ * file that was distributed with this source code. */ -use Doctrine\Common\Annotations\AnnotationRegistry; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler; // Detect if we need to serialize deprecations to a file. @@ -27,14 +26,6 @@ // Enforce a consistent locale setlocale(\LC_ALL, 'C'); -if (!class_exists(AnnotationRegistry::class, false) && class_exists(AnnotationRegistry::class)) { - if (method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { - AnnotationRegistry::registerUniqueLoader('class_exists'); - } elseif (method_exists(AnnotationRegistry::class, 'registerLoader')) { - AnnotationRegistry::registerLoader('class_exists'); - } -} - if ('disabled' !== getenv('SYMFONY_DEPRECATIONS_HELPER')) { DeprecationErrorHandler::register(getenv('SYMFONY_DEPRECATIONS_HELPER')); } diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index b0ccda04315f1..d576fb99986ff 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -28,6 +28,7 @@ "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" }, "conflict": { + "doctrine/annotations": "<1.10", "phpunit/phpunit": "<7.5|9.1.2" }, "autoload": { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index a75bd61f0511c..d42f817514ecb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -12,7 +12,6 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; use Composer\InstalledVersions; -use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\Reader; use Http\Client\HttpClient; use phpDocumentor\Reflection\DocBlockFactoryInterface; @@ -1643,15 +1642,6 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde $loader->load('annotations.php'); - if (!method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { - if (method_exists(AnnotationRegistry::class, 'registerLoader')) { - $container->getDefinition('annotations.dummy_registry') - ->setMethodCalls([['registerLoader', ['class_exists']]]); - } else { - $container->removeDefinition('annotations.dummy_registry'); - } - } - if ('none' === $config['cache']) { $container->removeDefinition('annotations.cached_reader'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php index 65a3f4e8ffd90..0f561d9d3a571 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php @@ -12,7 +12,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Doctrine\Common\Annotations\AnnotationReader; -use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\PsrCachedReader; use Doctrine\Common\Annotations\Reader; use Symfony\Bundle\FrameworkBundle\CacheWarmer\AnnotationsCacheWarmer; @@ -23,13 +22,7 @@ return static function (ContainerConfigurator $container) { $container->services() ->set('annotations.reader', AnnotationReader::class) - ->call('addGlobalIgnoredName', [ - 'required', - service('annotations.dummy_registry')->ignoreOnInvalid(), // dummy arg to register class_exists as annotation loader only when required - ]) - - ->set('annotations.dummy_registry', AnnotationRegistry::class) - ->call('registerUniqueLoader', ['class_exists']) + ->call('addGlobalIgnoredName', ['required']) ->set('annotations.cached_reader', PsrCachedReader::class) ->args([ diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php index e2843a0a39843..2af4032ad28ef 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Routing\Tests\Loader; use Doctrine\Common\Annotations\AnnotationReader; -use Doctrine\Common\Annotations\AnnotationRegistry; use Symfony\Component\Routing\Loader\AnnotationClassLoader; use Symfony\Component\Routing\Route; @@ -26,9 +25,6 @@ protected function configureRoute(Route $route, \ReflectionClass $class, \Reflec { } }; - if (method_exists(AnnotationRegistry::class, 'registerLoader')) { - AnnotationRegistry::registerLoader('class_exists'); - } } public function testDefaultRouteName() From 4917528d9ada1b3676a8bd7920e35622d73b78ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 3 Nov 2022 13:03:23 +0100 Subject: [PATCH 080/475] Resolve DateTime value using the clock --- .../FrameworkBundle/Resources/config/web.php | 3 + src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../DateTimeValueResolver.php | 22 +++++-- .../DateTimeValueResolverTest.php | 61 ++++++++++++++----- .../Component/HttpKernel/composer.json | 1 + 5 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php index 82ceb2e077da4..cd4cdffa10ade 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php @@ -55,6 +55,9 @@ ->tag('controller.argument_value_resolver', ['priority' => 100]) ->set('argument_resolver.datetime', DateTimeValueResolver::class) + ->args([ + service('clock')->nullOnInvalid(), + ]) ->tag('controller.argument_value_resolver', ['priority' => 100]) ->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class) diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 02c9da3d12b0e..c823fbd0cb5c4 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead * `FileProfilerStorage` removes profiles automatically after two days * Add `#[HttpStatus]` for defining status codes for exceptions + * Use an instance of `Psr\Clock\ClockInterface` to generate the current date time in `DateTimeValueResolver` 6.2 --- diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php index 8fd7015ad041d..a73a7e1b47a27 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Psr\Clock\ClockInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\MapDateTime; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; @@ -26,6 +27,11 @@ */ final class DateTimeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { + public function __construct( + private readonly ?ClockInterface $clock = null, + ) { + } + /** * @deprecated since Symfony 6.2, use resolve() instead */ @@ -45,12 +51,18 @@ public function resolve(Request $request, ArgumentMetadata $argument): array $value = $request->attributes->get($argument->getName()); $class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType(); - if ($value instanceof \DateTimeInterface) { - return [$value instanceof $class ? $value : $class::createFromInterface($value)]; + if (!$value) { + if ($argument->isNullable()) { + return [null]; + } + if (!$this->clock) { + return [new $class()]; + } + $value = $this->clock->now(); } - if ($argument->isNullable() && !$value) { - return [null]; + if ($value instanceof \DateTimeInterface) { + return [$value instanceof $class ? $value : $class::createFromInterface($value)]; } $format = null; @@ -71,7 +83,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): array $value = '@'.$value; } try { - $date = new $class($value ?? 'now'); + $date = new $class($value); } catch (\Exception) { $date = false; } diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php index 770324d59b7e0..4e43cfd11b083 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpKernel\Tests\Controller\ArgumentResolver; use PHPUnit\Framework\TestCase; +use Symfony\Component\Clock\MockClock; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\MapDateTime; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver; @@ -34,9 +35,12 @@ protected function tearDown(): void public static function getTimeZones() { - yield ['UTC']; - yield ['Etc/GMT+9']; - yield ['Etc/GMT-14']; + yield ['UTC', false]; + yield ['Etc/GMT+9', false]; + yield ['Etc/GMT-14', false]; + yield ['UTC', true]; + yield ['Etc/GMT+9', true]; + yield ['Etc/GMT-14', true]; } public static function getClasses() @@ -78,10 +82,10 @@ public function testUnsupportedArgument() /** * @dataProvider getTimeZones */ - public function testFullDate(string $timezone) + public function testFullDate(string $timezone, bool $withClock) { date_default_timezone_set($timezone); - $resolver = new DateTimeValueResolver(); + $resolver = new DateTimeValueResolver($withClock ? new MockClock() : null); $argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null); $request = self::requestWithAttributes(['dummy' => '2012-07-21 00:00:00']); @@ -97,10 +101,10 @@ public function testFullDate(string $timezone) /** * @dataProvider getTimeZones */ - public function testUnixTimestamp(string $timezone) + public function testUnixTimestamp(string $timezone, bool $withClock) { date_default_timezone_set($timezone); - $resolver = new DateTimeValueResolver(); + $resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null); $argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null); $request = self::requestWithAttributes(['dummy' => '989541720']); @@ -127,21 +131,46 @@ public function testNullableWithEmptyAttribute() } /** - * @dataProvider getTimeZones + * @param class-string<\DateTimeInterface> $class + * + * @dataProvider getClasses */ - public function testNow(string $timezone) + public function testNow(string $class) { - date_default_timezone_set($timezone); + date_default_timezone_set($timezone = 'Etc/GMT+9'); $resolver = new DateTimeValueResolver(); - $argument = new ArgumentMetadata('dummy', \DateTime::class, false, false, null, false); + $argument = new ArgumentMetadata('dummy', $class, false, false, null, false); $request = self::requestWithAttributes(['dummy' => null]); $results = $resolver->resolve($request, $argument); $this->assertCount(1, $results); - $this->assertEquals('0', $results[0]->diff(new \DateTimeImmutable())->format('%s')); + $this->assertInstanceOf($class, $results[0]); $this->assertSame($timezone, $results[0]->getTimezone()->getName(), 'Default timezone'); + $this->assertEquals('0', $results[0]->diff(new \DateTimeImmutable())->format('%s')); + } + + /** + * @param class-string<\DateTimeInterface> $class + * + * @dataProvider getClasses + */ + public function testNowWithClock(string $class) + { + date_default_timezone_set('Etc/GMT+9'); + $clock = new MockClock('2022-02-20 22:20:02'); + $resolver = new DateTimeValueResolver($clock); + + $argument = new ArgumentMetadata('dummy', $class, false, false, null, false); + $request = self::requestWithAttributes(['dummy' => null]); + + $results = $resolver->resolve($request, $argument); + + $this->assertCount(1, $results); + $this->assertInstanceOf($class, $results[0]); + $this->assertSame('UTC', $results[0]->getTimezone()->getName(), 'Default timezone'); + $this->assertEquals($clock->now(), $results[0]); } /** @@ -181,10 +210,10 @@ public function testCustomClass() /** * @dataProvider getTimeZones */ - public function testDateTimeImmutable(string $timezone) + public function testDateTimeImmutable(string $timezone, bool $withClock) { date_default_timezone_set($timezone); - $resolver = new DateTimeValueResolver(); + $resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null); $argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null); $request = self::requestWithAttributes(['dummy' => '2016-09-08 00:00:00 +05:00']); @@ -200,10 +229,10 @@ public function testDateTimeImmutable(string $timezone) /** * @dataProvider getTimeZones */ - public function testWithFormat(string $timezone) + public function testWithFormat(string $timezone, bool $withClock) { date_default_timezone_set($timezone); - $resolver = new DateTimeValueResolver(); + $resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null); $argument = new ArgumentMetadata('dummy', \DateTimeInterface::class, false, false, null, false, [ MapDateTime::class => new MapDateTime('m-d-y H:i:s'), diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 2c92557cbb6fb..41ef39d3ae749 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -26,6 +26,7 @@ }, "require-dev": { "symfony/browser-kit": "^5.4|^6.0", + "symfony/clock": "^6.2", "symfony/config": "^6.1", "symfony/console": "^5.4|^6.0", "symfony/css-selector": "^5.4|^6.0", From 86db5052deb312c2ae9ca3d46de562c8d6b64fb2 Mon Sep 17 00:00:00 2001 From: Daif Date: Mon, 12 Dec 2022 17:16:30 +0100 Subject: [PATCH 081/475] CardsV1 is deprecated we must use cardsV2 instead. Based on google developers api documentation. https://developers.google.com/chat/api/reference/rest/v1/cards-v1 CardsV1 is deprecated we must use cardsV2 instead. --- .../Notifier/Bridge/GoogleChat/CHANGELOG.md | 5 +++ .../Bridge/GoogleChat/GoogleChatOptions.php | 14 ++++++ .../Tests/GoogleChatOptionsTest.php | 44 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md index 5759f578770fe..c01ece62d544a 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate `GoogleChatOptions::card()` in favor of `cardV2()` + 5.3 --- diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php index da479d1311a93..0550d725a0d6c 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/GoogleChatOptions.php @@ -62,15 +62,29 @@ public function toArray(): array } /** + * @deprecated since Symfony 6.3, use "cardV2()" instead + * * @return $this */ public function card(array $card): static { + trigger_deprecation('symfony/google-chat-notifier', '6.3', '"%s()" is deprecated, use "cardV2()" instead.', __METHOD__); + $this->options['cards'][] = $card; return $this; } + /** + * @return $this + */ + public function cardV2(array $card): static + { + $this->options['cardsV2'][] = $card; + + return $this; + } + /** * @return $this */ diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatOptionsTest.php b/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatOptionsTest.php index 691958c46a079..00984a60044c9 100644 --- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatOptionsTest.php +++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/Tests/GoogleChatOptionsTest.php @@ -16,6 +16,9 @@ final class GoogleChatOptionsTest extends TestCase { + /** + * @group legacy + */ public function testToArray() { $options = new GoogleChatOptions(); @@ -34,6 +37,47 @@ public function testToArray() $this->assertSame($expected, $options->toArray()); } + public function testToArrayWithCardV2() + { + $options = new GoogleChatOptions(); + + $cardV2 = [ + 'header' => [ + 'title' => 'Sasha', + 'subtitle' => 'Software Engineer', + 'imageUrl' => 'https://developers.google.com/chat/images/quickstart-app-avatar.png', + 'imageType' => 'CIRCLE', + 'imageAltText' => 'Avatar for Sasha', + ], + 'sections' => [ + [ + 'header' => 'Contact Info', + 'collapsible' => true, + 'widgets' => [ + 'decoratedText' => [ + 'startIcon' => ['knownIcon' => 'EMAIL'], + 'text' => 'sasha@example.com', + ], + ], + ], + ], + ]; + + $options + ->text('Hello Bot') + ->cardV2($cardV2) + ; + + $expected = [ + 'text' => 'Hello Bot', + 'cardsV2' => [ + $cardV2, + ], + ]; + + $this->assertSame($expected, $options->toArray()); + } + public function testOptionsWithThread() { $thread = 'fgh.ijk'; From 57c2365ea4003c74744eef8c3dfe5273c3ba001c Mon Sep 17 00:00:00 2001 From: Sergey Rabochiy Date: Sat, 17 Dec 2022 01:23:10 +0700 Subject: [PATCH 082/475] [DependencyInjection] Deprecate integers keys in "service_locator" config --- UPGRADE-6.3.md | 1 + .../DependencyInjection/CHANGELOG.md | 1 + .../Configurator/ContainerConfigurator.php | 8 ++++- .../Loader/XmlFileLoader.php | 5 ++++ .../Loader/YamlFileLoader.php | 4 +++ ...services_with_service_locator_argument.php | 29 +++++++++++++++++++ ...services_with_service_locator_argument.xml | 29 +++++++++++++++++++ ...services_with_service_locator_argument.yml | 28 ++++++++++++++++++ .../Tests/Loader/PhpFileLoaderTest.php | 26 +++++++++++++++++ .../Tests/Loader/XmlFileLoaderTest.php | 24 +++++++++++++++ .../Tests/Loader/YamlFileLoaderTest.php | 24 +++++++++++++++ 11 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services_with_service_locator_argument.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_service_locator_argument.xml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_service_locator_argument.yml diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index 77c7858198d80..58949b88e408d 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -5,6 +5,7 @@ DependencyInjection ------------------- * Deprecate `PhpDumper` options `inline_factories_parameter` and `inline_class_loader_parameter`, use `inline_factories` and `inline_class_loader` instead + * Deprecate undefined and numeric keys with `service_locator` config, use string aliases instead HttpKernel ---------- diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 6f0f9c1056b83..e1d0dda20d6ff 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Deprecate `PhpDumper` options `inline_factories_parameter` and `inline_class_loader_parameter` * Add `RemoveBuildParametersPass`, which removes parameters starting with a dot during compilation * Add support for nesting autowiring-related attributes into `#[Autowire(...)]` + * Deprecate undefined and numeric keys with `service_locator` config 6.2 --- diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php index 3065a94b3bff0..91acd9a10ea7d 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php @@ -123,7 +123,13 @@ function inline_service(string $class = null): InlineServiceConfigurator */ function service_locator(array $values): ServiceLocatorArgument { - return new ServiceLocatorArgument(AbstractConfigurator::processValue($values, true)); + $values = AbstractConfigurator::processValue($values, true); + + if (isset($values[0])) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Using integers as keys in a "service_locator()" argument is deprecated. The keys will default to the IDs of the original services in 7.0.'); + } + + return new ServiceLocatorArgument($values); } /** diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index ab76eb9e0df08..7c0ea3261300b 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -526,6 +526,11 @@ private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file break; case 'service_locator': $arg = $this->getArgumentsAsPhp($arg, $name, $file); + + if (isset($arg[0])) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Skipping "key" argument or using integers as values in a "service_locator" tag is deprecated. The keys will default to the IDs of the original services in 7.0.'); + } + $arguments[$key] = new ServiceLocatorArgument($arg); break; case 'tagged': diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 80a58d39f4f6b..ad61c14437d1a 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -814,6 +814,10 @@ private function resolveServices(mixed $value, string $file, bool $isParameter = $argument = $this->resolveServices($argument, $file, $isParameter); + if (isset($argument[0])) { + trigger_deprecation('symfony/dependency-injection', '6.3', 'Using integers as keys in a "!service_locator" tag is deprecated. The keys will default to the IDs of the original services in 7.0.'); + } + return new ServiceLocatorArgument($argument); } if (\in_array($value->getTag(), ['tagged', 'tagged_iterator', 'tagged_locator'], true)) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services_with_service_locator_argument.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services_with_service_locator_argument.php new file mode 100644 index 0000000000000..58757abc4b326 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services_with_service_locator_argument.php @@ -0,0 +1,29 @@ +services()->defaults()->public(); + + $services->set('foo_service', \stdClass::class); + + $services->set('bar_service', \stdClass::class); + + $services->set('locator_dependent_service_indexed', \ArrayObject::class) + ->args([service_locator([ + 'foo' => service('foo_service'), + 'bar' => service('bar_service'), + ])]); + + $services->set('locator_dependent_service_not_indexed', \ArrayObject::class) + ->args([service_locator([ + service('foo_service'), + service('bar_service'), + ])]); + + $services->set('locator_dependent_service_mixed', \ArrayObject::class) + ->args([service_locator([ + 'foo' => service('foo_service'), + service('bar_service'), + ])]); +}; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_service_locator_argument.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_service_locator_argument.xml new file mode 100644 index 0000000000000..f98ca9e5a01d9 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_service_locator_argument.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_service_locator_argument.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_service_locator_argument.yml new file mode 100644 index 0000000000000..b0309d3eeab9a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_service_locator_argument.yml @@ -0,0 +1,28 @@ + +services: + foo_service: + class: stdClass + + bar_service: + class: stdClass + + locator_dependent_service_indexed: + class: ArrayObject + arguments: + - !service_locator + 'foo': '@foo_service' + 'bar': '@bar_service' + + locator_dependent_service_not_indexed: + class: ArrayObject + arguments: + - !service_locator + - '@foo_service' + - '@bar_service' + + locator_dependent_service_mixed: + class: ArrayObject + arguments: + - !service_locator + 'foo': '@foo_service' + '0': '@bar_service' diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php index ef153e178bc03..15f88324dccfa 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php @@ -14,18 +14,23 @@ require_once __DIR__.'/../Fixtures/includes/AcmeExtension.php'; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Config\Builder\ConfigBuilderGenerator; use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\Dumper\YamlDumper; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum; class PhpFileLoaderTest extends TestCase { + use ExpectDeprecationTrait; + public function testSupports() { $loader = new PhpFileLoader(new ContainerBuilder(), new FileLocator()); @@ -200,4 +205,25 @@ public function testWhenEnv() $loader->load($fixtures.'/config/when_env.php'); } + + /** + * @group legacy + */ + public function testServiceWithServiceLocatorArgument() + { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Using integers as keys in a "service_locator()" argument is deprecated. The keys will default to the IDs of the original services in 7.0.'); + + $fixtures = realpath(__DIR__.'/../Fixtures'); + $loader = new PhpFileLoader($container = new ContainerBuilder(), new FileLocator()); + $loader->load($fixtures.'/config/services_with_service_locator_argument.php'); + + $values = ['foo' => new Reference('foo_service'), 'bar' => new Reference('bar_service')]; + $this->assertEquals([new ServiceLocatorArgument($values)], $container->getDefinition('locator_dependent_service_indexed')->getArguments()); + + $values = [new Reference('foo_service'), new Reference('bar_service')]; + $this->assertEquals([new ServiceLocatorArgument($values)], $container->getDefinition('locator_dependent_service_not_indexed')->getArguments()); + + $values = ['foo' => new Reference('foo_service'), 0 => new Reference('bar_service')]; + $this->assertEquals([new ServiceLocatorArgument($values)], $container->getDefinition('locator_dependent_service_mixed')->getArguments()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 9207742bc25f1..56a5fe0be4871 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Loader; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; use Symfony\Component\Config\Exception\LoaderLoadException; use Symfony\Component\Config\FileLocator; @@ -47,6 +48,8 @@ class XmlFileLoaderTest extends TestCase { + use ExpectDeprecationTrait; + protected static $fixturesPath; public static function setUpBeforeClass(): void @@ -421,6 +424,27 @@ public function testParseTaggedArgumentsWithIndexBy() $this->assertEquals(new ServiceLocatorArgument($taggedIterator3), $container->getDefinition('foo3_tagged_locator')->getArgument(0)); } + /** + * @group legacy + */ + public function testServiceWithServiceLocatorArgument() + { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Skipping "key" argument or using integers as values in a "service_locator" tag is deprecated. The keys will default to the IDs of the original services in 7.0.'); + + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('services_with_service_locator_argument.xml'); + + $values = ['foo' => new Reference('foo_service'), 'bar' => new Reference('bar_service')]; + $this->assertEquals([new ServiceLocatorArgument($values)], $container->getDefinition('locator_dependent_service_indexed')->getArguments()); + + $values = [new Reference('foo_service'), new Reference('bar_service')]; + $this->assertEquals([new ServiceLocatorArgument($values)], $container->getDefinition('locator_dependent_service_not_indexed')->getArguments()); + + $values = ['foo' => new Reference('foo_service'), 0 => new Reference('bar_service')]; + $this->assertEquals([new ServiceLocatorArgument($values)], $container->getDefinition('locator_dependent_service_mixed')->getArguments()); + } + public function testParseServiceClosure() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 952b2dc3fbb2c..9515bfee92099 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Loader; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; use Symfony\Component\Config\Exception\LoaderLoadException; use Symfony\Component\Config\FileLocator; @@ -45,6 +46,8 @@ class YamlFileLoaderTest extends TestCase { + use ExpectDeprecationTrait; + protected static $fixturesPath; public static function setUpBeforeClass(): void @@ -408,6 +411,27 @@ public function testTaggedArgumentsWithIndex() $this->assertEquals(new ServiceLocatorArgument($taggedIterator), $container->getDefinition('bar_service_tagged_locator')->getArgument(0)); } + /** + * @group legacy + */ + public function testServiceWithServiceLocatorArgument() + { + $this->expectDeprecation('Since symfony/dependency-injection 6.3: Using integers as keys in a "!service_locator" tag is deprecated. The keys will default to the IDs of the original services in 7.0.'); + + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('services_with_service_locator_argument.yml'); + + $values = ['foo' => new Reference('foo_service'), 'bar' => new Reference('bar_service')]; + $this->assertEquals([new ServiceLocatorArgument($values)], $container->getDefinition('locator_dependent_service_indexed')->getArguments()); + + $values = [new Reference('foo_service'), new Reference('bar_service')]; + $this->assertEquals([new ServiceLocatorArgument($values)], $container->getDefinition('locator_dependent_service_not_indexed')->getArguments()); + + $values = ['foo' => new Reference('foo_service'), 0 => new Reference('bar_service')]; + $this->assertEquals([new ServiceLocatorArgument($values)], $container->getDefinition('locator_dependent_service_mixed')->getArguments()); + } + public function testParseServiceClosure() { $container = new ContainerBuilder(); From b5eda47d913b5bfcc5a383da32fc92ac124ab880 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Thu, 15 Dec 2022 16:14:57 +0100 Subject: [PATCH 083/475] [FrameworkBundle] Deprecate `framework:exceptions` XML tag --- UPGRADE-6.3.md | 31 +++++++++++++++++++ .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 2 ++ 3 files changed, 34 insertions(+) diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index 58949b88e408d..d3c51e2acc394 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -7,6 +7,37 @@ DependencyInjection * Deprecate `PhpDumper` options `inline_factories_parameter` and `inline_class_loader_parameter`, use `inline_factories` and `inline_class_loader` instead * Deprecate undefined and numeric keys with `service_locator` config, use string aliases instead +FrameworkBundle +--------------- + + * Deprecate `framework:exceptions` tag, unwrap it and replace `framework:exception` tags' `name` attribute by `class` + + Before: + ```xml + + + + + + + ``` + + After: + ```xml + + + + + ``` + HttpKernel ---------- diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 2967592e00109..443b2e2792371 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Allow to avoid `limit` definition in a RateLimiter configuration when using the `no_limit` policy * Add `--format` option to the `debug:config` command * Add support to pass namespace wildcard in `framework.messenger.routing` + * Deprecate `framework:exceptions` tag, unwrap it and replace `framework:exception` tags' `name` attribute by `class` 6.2 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index f83969ea48d6b..44e812e735538 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1202,6 +1202,8 @@ private function addExceptionsSection(ArrayNodeDefinition $rootNode) return $v; } + trigger_deprecation('symfony/framework-bundle', '6.3', '"framework:exceptions" tag is deprecated. Unwrap it and replace your "framework:exception" tags\' "name" attribute by "class".'); + $v = $v['exception']; unset($v['exception']); From f0aa02ac53fd0d52e3410e681d95ad56e0d240c4 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 22 Dec 2022 13:47:53 +0100 Subject: [PATCH 084/475] [WebProfilerBundle] Misc code updates in controller and templates --- .../Controller/ProfilerController.php | 38 +++++-------------- .../views/Collector/events.html.twig | 6 +-- .../Resources/views/Collector/form.html.twig | 12 ++---- .../views/Collector/logger.html.twig | 10 +---- .../views/Collector/mailer.html.twig | 2 +- .../views/Collector/messenger.html.twig | 10 ++--- .../views/Collector/serializer.html.twig | 14 +++---- .../Resources/views/Collector/time.html.twig | 23 ++++------- .../views/Collector/translation.html.twig | 8 ++-- .../views/Profiler/results.html.twig | 8 ++-- 10 files changed, 40 insertions(+), 91 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index 83257e26f8c3a..be5b7883285ec 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -172,38 +172,18 @@ public function searchBarAction(Request $request): Response $this->cspHandler?->disableCsp(); - if (!$request->hasSession()) { - $ip = - $method = - $statusCode = - $url = - $start = - $end = - $limit = - $token = null; - } else { - $session = $request->getSession(); - - $ip = $request->query->get('ip', $session->get('_profiler_search_ip')); - $method = $request->query->get('method', $session->get('_profiler_search_method')); - $statusCode = $request->query->get('status_code', $session->get('_profiler_search_status_code')); - $url = $request->query->get('url', $session->get('_profiler_search_url')); - $start = $request->query->get('start', $session->get('_profiler_search_start')); - $end = $request->query->get('end', $session->get('_profiler_search_end')); - $limit = $request->query->get('limit', $session->get('_profiler_search_limit')); - $token = $request->query->get('token', $session->get('_profiler_search_token')); - } + $session = $request->hasSession() ? $request->getSession() : null; return new Response( $this->twig->render('@WebProfiler/Profiler/search.html.twig', [ - 'token' => $token, - 'ip' => $ip, - 'method' => $method, - 'status_code' => $statusCode, - 'url' => $url, - 'start' => $start, - 'end' => $end, - 'limit' => $limit, + 'token' => $request->query->get('token', $session?->get('_profiler_search_token')), + 'ip' => $request->query->get('ip', $session?->get('_profiler_search_ip')), + 'method' => $request->query->get('method', $session?->get('_profiler_search_method')), + 'status_code' => $request->query->get('status_code', $session?->get('_profiler_search_status_code')), + 'url' => $request->query->get('url', $session?->get('_profiler_search_url')), + 'start' => $request->query->get('start', $session?->get('_profiler_search_start')), + 'end' => $request->query->get('end', $session?->get('_profiler_search_end')), + 'limit' => $request->query->get('limit', $session?->get('_profiler_search_limit')), 'request' => $request, 'render_hidden_by_default' => false, ]), diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig index 33f33b235963a..8dadefa661745 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig @@ -1,7 +1,5 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} - {% block menu %} {{ source('@WebProfiler/Icon/event.svg') }} @@ -22,7 +20,7 @@

Called Listeners {{ collector.calledlisteners|length }}

- {{ helper.render_table(collector.calledlisteners) }} + {{ _self.render_table(collector.calledlisteners) }}
@@ -41,7 +39,7 @@

{% else %} - {{ helper.render_table(collector.notcalledlisteners) }} + {{ _self.render_table(collector.notcalledlisteners) }} {% endif %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig index 81973186f563b..3dcd475dcd7ea 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -1,7 +1,5 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% from _self import form_tree_entry, form_tree_details %} - {% block toolbar %} {% if collector.data.nb_errors > 0 or collector.data.forms|length %} {% set status_color = collector.data.nb_errors ? 'red' %} @@ -180,14 +178,14 @@
    {% for formName, formData in collector.data.forms %} - {{ form_tree_entry(formName, formData, true) }} + {{ _self.form_tree_entry(formName, formData, true) }} {% endfor %}
{% for formName, formData in collector.data.forms %} - {{ form_tree_details(formName, formData, collector.data.forms_by_hash, loop.first) }} + {{ _self.form_tree_details(formName, formData, collector.data.forms_by_hash, loop.first) }} {% endfor %}
{% else %} @@ -416,7 +414,6 @@ {% endblock %} {% macro form_tree_entry(name, data, is_root) %} - {% import _self as tree %} {% set has_error = data.errors is defined and data.errors|length > 0 %}
  • @@ -438,7 +435,7 @@ {% if data.children is not empty %}
      {% for childName, childData in data.children %} - {{ tree.form_tree_entry(childName, childData, false) }} + {{ _self.form_tree_entry(childName, childData, false) }} {% endfor %}
    {% endif %} @@ -446,7 +443,6 @@ {% endmacro %} {% macro form_tree_details(name, data, forms_by_hash, show) %} - {% import _self as tree %}

    {{ name|default('(no name)') }}

    {% if data.type_class is defined %} @@ -509,7 +505,7 @@
    {% for childName, childData in data.children %} - {{ tree.form_tree_details(childName, childData, forms_by_hash) }} + {{ _self.form_tree_details(childName, childData, forms_by_hash) }} {% endfor %} {% endmacro %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index dc9abc3e00cb4..385c80795fdd9 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -1,7 +1,5 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} - {% block toolbar %} {% if collector.counterrors or collector.countdeprecations or collector.countwarnings %} {% set icon %} @@ -163,7 +161,7 @@ - {{ helper.render_log_message('debug', loop.index, log) }} + {{ _self.render_log_message('debug', loop.index, log) }} {% endfor %} @@ -177,11 +175,7 @@ {% endif %} - {% set compilerLogTotal = 0 %} - {% for logs in collector.compilerLogs %} - {% set compilerLogTotal = compilerLogTotal + logs|length %} - {% endfor %} - + {% set compilerLogTotal = collector.compilerLogs|reduce((total, logs) => total + logs|length, 0) %}

    Container Compilation Logs ({{ compilerLogTotal }})

    diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig index 651c2a1626198..6435cf99e102a 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig @@ -151,7 +151,7 @@ {% if message.attachments %}
    {% set num_of_attachments = message.attachments|length %} - {% set total_attachments_size_in_bytes = message.attachments|reduce((total_size, attachment) => total_size + attachment.body|length) %} + {% set total_attachments_size_in_bytes = message.attachments|reduce((total_size, attachment) => total_size + attachment.body|length, 0) %}

    {{ source('@WebProfiler/Icon/attachment.svg') }} Attachments ({{ num_of_attachments }} file{{ num_of_attachments > 1 ? 's' }} / {{ _self.render_file_size_humanized(total_attachments_size_in_bytes) }}) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig index 1dc5accf1c3a4..cd1b9ece321ed 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig @@ -1,7 +1,5 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} - {% block toolbar %} {% if collector.messages|length > 0 %} {% set status_color = collector.exceptionsCount ? 'red' %} @@ -61,8 +59,6 @@ {% endblock %} {% block panel %} - {% import _self as helper %} -

    Messages

    {% if collector.messages is empty %} @@ -71,7 +67,7 @@
    {% elseif 1 == collector.buses|length %}

    Ordered list of dispatched messages across all your buses

    - {{ helper.render_bus_messages(collector.messages, true) }} + {{ _self.render_bus_messages(collector.messages, true) }} {% else %}
    @@ -81,7 +77,7 @@

    Ordered list of dispatched messages across all your buses

    - {{ helper.render_bus_messages(messages, true) }} + {{ _self.render_bus_messages(messages, true) }}
    @@ -93,7 +89,7 @@

    Ordered list of messages dispatched on the {{ bus }} bus

    - {{ helper.render_bus_messages(messages) }} + {{ _self.render_bus_messages(messages) }}
    {% endfor %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig index 455c49839d296..238444a0052ef 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig @@ -1,7 +1,5 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} - {% block toolbar %} {% if collector.handledCount > 0 %} {% set icon %} @@ -89,14 +87,14 @@
    - {{ helper.render_serialize_tab(collector.data, true) }} - {{ helper.render_serialize_tab(collector.data, false) }} + {{ _self.render_serialize_tab(collector.data, true) }} + {{ _self.render_serialize_tab(collector.data, false) }} - {{ helper.render_normalize_tab(collector.data, true) }} - {{ helper.render_normalize_tab(collector.data, false) }} + {{ _self.render_normalize_tab(collector.data, true) }} + {{ _self.render_normalize_tab(collector.data, false) }} - {{ helper.render_encode_tab(collector.data, true) }} - {{ helper.render_encode_tab(collector.data, false) }} + {{ _self.render_encode_tab(collector.data, true) }} + {{ _self.render_encode_tab(collector.data, false) }}
    {% endif %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig index 9db62e5b439a7..57f85cdfe9ec3 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig @@ -1,7 +1,5 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} - {% block toolbar %} {% set has_time_events = collector.events|length > 0 %} {% set total_time = has_time_events ? '%.0f'|format(collector.duration) : 'n/a' %} @@ -70,14 +68,9 @@ Sub-Request{{ profile.children|length > 1 ? 's' }} - {% if has_time_events %} - {% set subrequests_time = 0 %} - {% for child in profile.children %} - {% set subrequests_time = subrequests_time + child.getcollector('time').events.__section__.duration %} - {% endfor %} - {% else %} - {% set subrequests_time = 'n/a' %} - {% endif %} + {% set subrequests_time = has_time_events + ? profile.children|reduce((total, child) => total + child.getcollector('time').events.__section__.duration, 0) + : 'n/a' %}
    {{ subrequests_time }} ms @@ -124,7 +117,7 @@
  • {% endif %} - {{ helper.display_timeline(token, collector.events, collector.events.__section__.origin) }} + {{ _self.display_timeline(token, collector.events, collector.events.__section__.origin) }} {% if profile.children|length %}

    Note: sections with a striped background correspond to sub-requests.

    @@ -138,7 +131,7 @@ {{ events.__section__.duration }} ms - {{ helper.display_timeline(child.token, events, collector.events.__section__.origin) }} + {{ _self.display_timeline(child.token, events, collector.events.__section__.origin) }} {% endfor %} {% endif %} @@ -159,12 +152,11 @@ {% macro dump_request_data(token, events, origin) %} {% autoescape 'js' %} -{% from _self import dump_events %} { id: "{{ token }}", left: {{ "%F"|format(events.__section__.origin - origin) }}, end: "{{ '%F'|format(events.__section__.endtime) }}", - events: [ {{ dump_events(events) }} ], + events: [ {{ _self.dump_events(events) }} ], } {% endautoescape %} {% endmacro %} @@ -199,7 +191,6 @@ {% endmacro %} {% macro display_timeline(token, events, origin) %} -{% import _self as helper %}
    @@ -212,7 +203,7 @@ new SvgRenderer(document.getElementById('timeline-{{ token }}')), new Legend(document.getElementById('legend-{{ token }}'), theme), document.getElementById('threshold'), - {{ helper.dump_request_data(token, events, origin) }} + {{ _self.dump_request_data(token, events, origin) }} ); }); {% endautoescape %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig index 13503feeb4c05..c8190f5bfec66 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig @@ -1,7 +1,5 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} - {% block toolbar %} {% if collector.messages|length %} {% set icon %} @@ -105,7 +103,7 @@
    {% else %} {% block defined_messages %} - {{ helper.render_table(messages_defined) }} + {{ _self.render_table(messages_defined) }} {% endblock %} {% endif %} @@ -126,7 +124,7 @@ {% else %} {% block fallback_messages %} - {{ helper.render_table(messages_fallback, true) }} + {{ _self.render_table(messages_fallback, true) }} {% endblock %} {% endif %} @@ -148,7 +146,7 @@ {% else %} {% block missing_messages %} - {{ helper.render_table(messages_missing) }} + {{ _self.render_table(messages_missing) }} {% endblock %} {% endif %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig index 0705b1e7270c0..ae18451444218 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig @@ -6,8 +6,6 @@ {%- endif -%} {% endmacro %} -{% import _self as helper %} - {% block summary %}

    Profile Search

    @@ -41,14 +39,14 @@ {{ result.status_code|default('n/a') }} - {{ result.ip }} {{ helper.profile_search_filter(request, result, 'ip') }} + {{ result.ip }} {{ _self.profile_search_filter(request, result, 'ip') }} - {{ result.method }} {{ helper.profile_search_filter(request, result, 'method') }} + {{ result.method }} {{ _self.profile_search_filter(request, result, 'method') }} {{ result.url }} - {{ helper.profile_search_filter(request, result, 'url') }} + {{ _self.profile_search_filter(request, result, 'url') }} {{ result.time|date('d-M-Y') }} From 01183483de5242320a5fe610a7f595bce2af9e25 Mon Sep 17 00:00:00 2001 From: Sylvain BEISSIER Date: Thu, 15 Dec 2022 20:33:02 +0100 Subject: [PATCH 085/475] [Validator] Add `getConstraint()` method to `ConstraintViolationInterface` --- UPGRADE-6.3.md | 6 ++++++ src/Symfony/Component/Validator/CHANGELOG.md | 1 + .../Component/Validator/ConstraintViolationInterface.php | 5 +++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index 77c7858198d80..f3b440e38b4b6 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -23,3 +23,9 @@ SecurityBundle -------------- * Deprecate enabling bundle and not configuring it + + +Validator +-------------- + +* Implementing the `ConstraintViolationInterface` without implementing the `getConstraint()` method is deprecated diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index ca83f3af0a3f1..fbd40a2077ebb 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.3 --- + * Add method `getConstraint()` to `ConstraintViolationInterface` * Add `Uuid::TIME_BASED_VERSIONS` to match that a UUID being validated embeds a timestamp * Add the `pattern` parameter in violations of the `Regex` constraint diff --git a/src/Symfony/Component/Validator/ConstraintViolationInterface.php b/src/Symfony/Component/Validator/ConstraintViolationInterface.php index 6000a7aa6aaab..6eb27974061d2 100644 --- a/src/Symfony/Component/Validator/ConstraintViolationInterface.php +++ b/src/Symfony/Component/Validator/ConstraintViolationInterface.php @@ -31,8 +31,9 @@ * * @author Bernhard Schussek * - * @method mixed getCause() Returns the cause of the violation. Not implementing it is deprecated since Symfony 6.2. - * @method string __toString() Converts the violation into a string for debugging purposes. Not implementing it is deprecated since Symfony 6.1. + * @method Constraint|null getConstraint() Returns the constraint whose validation caused the violation. Not implementing it is deprecated since Symfony 6.3. + * @method mixed getCause() Returns the cause of the violation. Not implementing it is deprecated since Symfony 6.2. + * @method string __toString() Converts the violation into a string for debugging purposes. Not implementing it is deprecated since Symfony 6.1. */ interface ConstraintViolationInterface { From 0a0a98aebbeca7008d2c30e97b49570b64c9bb27 Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Tue, 29 Nov 2022 13:06:44 +0100 Subject: [PATCH 086/475] [SecurityBundle] Rename `firewalls.logout.csrf_token_generator` to `firewalls.logout.csrf_token_manager` --- UPGRADE-6.3.md | 3 ++- .../Bundle/SecurityBundle/CHANGELOG.md | 1 + .../DependencyInjection/MainConfiguration.php | 23 ++++++++++++++---- .../DependencyInjection/SecurityExtension.php | 4 ++-- .../MainConfigurationTest.php | 24 +++++++++---------- .../app/CsrfFormLogin/base_config.yml | 2 +- 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index f7b956e5dbc69..3b664018a9111 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -60,4 +60,5 @@ SecurityBundle Validator -------------- -* Implementing the `ConstraintViolationInterface` without implementing the `getConstraint()` method is deprecated + * Implementing the `ConstraintViolationInterface` without implementing the `getConstraint()` method is deprecated + * Deprecate the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 4ab9103275bb4..def296d23df5e 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Add `StatelessAuthenticatorFactoryInterface` for authenticators targeting `stateless` firewalls only and that don't require a user provider * Modify "icon.svg" to improve accessibility for blind/low vision users * Make `Security::login()` return the authenticator response + * Deprecate the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead 6.2 --- diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index 25778ea851dd7..44d925c1f1c0b 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -217,12 +217,20 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->treatTrueLike([]) ->canBeUnset() ->beforeNormalization() - ->ifTrue(fn ($v): bool => \is_array($v) && (isset($v['csrf_token_generator']) xor isset($v['enable_csrf']))) + ->ifTrue(fn ($v): bool => isset($v['csrf_token_generator']) && !isset($v['csrf_token_manager'])) ->then(function (array $v): array { - if (isset($v['csrf_token_generator'])) { + $v['csrf_token_manager'] = $v['csrf_token_generator']; + + return $v; + }) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v): bool => \is_array($v) && (isset($v['csrf_token_manager']) xor isset($v['enable_csrf']))) + ->then(function (array $v): array { + if (isset($v['csrf_token_manager'])) { $v['enable_csrf'] = true; } elseif ($v['enable_csrf']) { - $v['csrf_token_generator'] = 'security.csrf.token_manager'; + $v['csrf_token_manager'] = 'security.csrf.token_manager'; } return $v; @@ -232,7 +240,14 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->booleanNode('enable_csrf')->defaultNull()->end() ->scalarNode('csrf_token_id')->defaultValue('logout')->end() ->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end() - ->scalarNode('csrf_token_generator')->end() + ->scalarNode('csrf_token_generator') + ->setDeprecated( + 'symfony/security-bundle', + '6.3', + 'The "%node%" option is deprecated. Use "csrf_token_manager" instead.' + ) + ->end() + ->scalarNode('csrf_token_manager')->end() ->scalarNode('path')->defaultValue('/logout')->end() ->scalarNode('target')->defaultValue('/')->end() ->booleanNode('invalidate_session')->defaultTrue()->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 9aa0293651b37..d1724dd81b621 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -458,7 +458,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ // add CSRF provider if ($firewall['logout']['enable_csrf']) { - $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_generator'])); + $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_manager'])); } // add session logout listener @@ -482,7 +482,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ $firewall['logout']['path'], $firewall['logout']['csrf_token_id'], $firewall['logout']['csrf_parameter'], - isset($firewall['logout']['csrf_token_generator']) ? new Reference($firewall['logout']['csrf_token_generator']) : null, + isset($firewall['logout']['csrf_token_manager']) ? new Reference($firewall['logout']['csrf_token_manager']) : null, false === $firewall['stateless'] && isset($firewall['context']) ? $firewall['context'] : null, ]) ; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php index 20bc11269fa6f..9765ff28338f1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php @@ -71,7 +71,7 @@ public function testCsrfAliases() 'firewalls' => [ 'stub' => [ 'logout' => [ - 'csrf_token_generator' => 'a_token_generator', + 'csrf_token_manager' => 'a_token_manager', 'csrf_token_id' => 'a_token_id', ], ], @@ -82,8 +82,8 @@ public function testCsrfAliases() $processor = new Processor(); $configuration = new MainConfiguration([], []); $processedConfig = $processor->processConfiguration($configuration, [$config]); - $this->assertArrayHasKey('csrf_token_generator', $processedConfig['firewalls']['stub']['logout']); - $this->assertEquals('a_token_generator', $processedConfig['firewalls']['stub']['logout']['csrf_token_generator']); + $this->assertArrayHasKey('csrf_token_manager', $processedConfig['firewalls']['stub']['logout']); + $this->assertEquals('a_token_manager', $processedConfig['firewalls']['stub']['logout']['csrf_token_manager']); $this->assertArrayHasKey('csrf_token_id', $processedConfig['firewalls']['stub']['logout']); $this->assertEquals('a_token_id', $processedConfig['firewalls']['stub']['logout']['csrf_token_id']); } @@ -92,13 +92,13 @@ public function testLogoutCsrf() { $config = [ 'firewalls' => [ - 'custom_token_generator' => [ + 'custom_token_manager' => [ 'logout' => [ - 'csrf_token_generator' => 'a_token_generator', + 'csrf_token_manager' => 'a_token_manager', 'csrf_token_id' => 'a_token_id', ], ], - 'default_token_generator' => [ + 'default_token_manager' => [ 'logout' => [ 'enable_csrf' => true, 'csrf_token_id' => 'a_token_id', @@ -121,18 +121,18 @@ public function testLogoutCsrf() $processedConfig = $processor->processConfiguration($configuration, [$config]); $assertions = [ - 'custom_token_generator' => [true, 'a_token_generator'], - 'default_token_generator' => [true, 'security.csrf.token_manager'], + 'custom_token_manager' => [true, 'a_token_manager'], + 'default_token_manager' => [true, 'security.csrf.token_manager'], 'disabled_csrf' => [false, null], 'empty' => [false, null], ]; - foreach ($assertions as $firewallName => [$enabled, $tokenGenerator]) { + foreach ($assertions as $firewallName => [$enabled, $tokenManager]) { $this->assertEquals($enabled, $processedConfig['firewalls'][$firewallName]['logout']['enable_csrf']); - if ($tokenGenerator) { - $this->assertEquals($tokenGenerator, $processedConfig['firewalls'][$firewallName]['logout']['csrf_token_generator']); + if ($tokenManager) { + $this->assertEquals($tokenManager, $processedConfig['firewalls'][$firewallName]['logout']['csrf_token_manager']); $this->assertEquals('a_token_id', $processedConfig['firewalls'][$firewallName]['logout']['csrf_token_id']); } else { - $this->assertArrayNotHasKey('csrf_token_generator', $processedConfig['firewalls'][$firewallName]['logout']); + $this->assertArrayNotHasKey('csrf_token_manager', $processedConfig['firewalls'][$firewallName]['logout']); } } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml index 069fece61756f..9f84b66ee67c1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml @@ -43,7 +43,7 @@ security: logout: path: /logout_path target: / - csrf_token_generator: security.csrf.token_manager + csrf_token_manager: security.csrf.token_manager access_control: - { path: .*, roles: IS_AUTHENTICATED_FULLY } From 377982f285cf021ce55ef7018dfc520d610fdaed Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 22 Dec 2022 18:12:21 +0100 Subject: [PATCH 087/475] Fix typo --- UPGRADE-6.3.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index 3b664018a9111..b626a000b508f 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -55,10 +55,9 @@ SecurityBundle -------------- * Deprecate enabling bundle and not configuring it - + * Deprecate the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead Validator --------------- +--------- * Implementing the `ConstraintViolationInterface` without implementing the `getConstraint()` method is deprecated - * Deprecate the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead From 5308c7c845fbd92770eda82f918813da75be1c29 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 22 Dec 2022 16:57:59 +0100 Subject: [PATCH 088/475] [HttpClient] Remove dead code --- src/Symfony/Component/HttpClient/Response/MockResponse.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php index e75a6ead8be38..59e73ba1ba176 100644 --- a/src/Symfony/Component/HttpClient/Response/MockResponse.php +++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php @@ -186,11 +186,6 @@ protected static function perform(ClientState $multi, array &$responses): void $chunk[1]->getHeaders(false); self::readResponse($response, $chunk[0], $chunk[1], $offset); $multi->handlesActivity[$id][] = new FirstChunk(); - $buffer = $response->requestOptions['buffer'] ?? null; - - if ($buffer instanceof \Closure && $response->content = $buffer($response->headers) ?: null) { - $response->content = \is_resource($response->content) ? $response->content : fopen('php://temp', 'w+'); - } } catch (\Throwable $e) { $multi->handlesActivity[$id][] = null; $multi->handlesActivity[$id][] = $e; From 983cd082d0cf698d5355a74ae27c74b2dc1fee40 Mon Sep 17 00:00:00 2001 From: rodmen Date: Mon, 19 Dec 2022 12:25:51 -0300 Subject: [PATCH 089/475] [DependencyInjection] Target Attribute must fail if the target does not exist --- .../DependencyInjection/Attribute/Target.php | 6 ++++-- .../Component/DependencyInjection/CHANGELOG.md | 1 + .../DependencyInjection/Compiler/AutowirePass.php | 12 ++++++++++-- .../Tests/Compiler/AutowirePassTest.php | 15 +++++++++++++++ .../RegisterServiceSubscribersPassTest.php | 2 +- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Attribute/Target.php b/src/Symfony/Component/DependencyInjection/Attribute/Target.php index 7751b3813bada..b935500e9737d 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/Target.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/Target.php @@ -28,13 +28,15 @@ public function __construct(string $name) $this->name = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name)))); } - public static function parseName(\ReflectionParameter $parameter): string + public static function parseName(\ReflectionParameter $parameter, self &$attribute = null): string { + $attribute = null; if (!$target = $parameter->getAttributes(self::class)[0] ?? null) { return $parameter->name; } - $name = $target->newInstance()->name; + $attribute = $target->newInstance(); + $name = $attribute->name; if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) { if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) { diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index e1d0dda20d6ff..81dec56e681f3 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Add `RemoveBuildParametersPass`, which removes parameters starting with a dot during compilation * Add support for nesting autowiring-related attributes into `#[Autowire(...)]` * Deprecate undefined and numeric keys with `service_locator` config + * Fail if Target attribute does not exist during compilation 6.2 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 09b0490adf4c8..66a175d76c267 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -128,7 +128,7 @@ private function doProcessValue(mixed $value, bool $isRoot = false): mixed return $this->processAttribute($attribute, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $value->getInvalidBehavior()); } - $value = new TypedReference($value->getType(), $value->getType(), $value->getInvalidBehavior(), $attribute->name); + $value = new TypedReference($value->getType(), $value->getType(), $value->getInvalidBehavior(), $attribute->name, [$attribute]); } if ($ref = $this->getAutowiredReference($value, true)) { return $ref; @@ -332,7 +332,8 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a } $getValue = function () use ($type, $parameter, $class, $method) { - if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, Target::parseName($parameter)), false)) { + $name = Target::parseName($parameter, $target); + if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $name, $target ? [$target] : []), false)) { $failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method)); if ($parameter->isDefaultValueAvailable()) { @@ -420,6 +421,10 @@ private function getAutowiredReference(TypedReference $reference, bool $filterTy } } } + + if ($reference->getAttributes()) { + return null; + } } if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) { @@ -544,6 +549,9 @@ private function createTypeNotFoundMessage(TypedReference $reference, string $la } $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found'); + } elseif ($reference->getAttributes()) { + $message = $label; + $label = sprintf('"#[Target(\'%s\')" on', $reference->getName()); } else { $alternatives = $this->createTypeAlternatives($this->container, $reference); $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index c766bed6aefa4..8224150357dda 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -1142,6 +1142,21 @@ public function testArgumentWithTarget() $this->assertSame(BarInterface::class.' $imageStorage', (string) $container->getDefinition('with_target')->getArgument(0)); } + public function testArgumentWithTypoTarget() + { + $container = new ContainerBuilder(); + + $container->register(BarInterface::class, BarInterface::class); + $container->register(BarInterface::class.' $iamgeStorage', BarInterface::class); + $container->register('with_target', WithTarget::class) + ->setAutowired(true); + + $this->expectException(AutowiringFailedException::class); + $this->expectExceptionMessage('Cannot autowire service "with_target": "#[Target(\'imageStorage\')" on argument "$bar" of method "Symfony\Component\DependencyInjection\Tests\Fixtures\WithTarget::__construct()"'); + + (new AutowirePass())->process($container); + } + public function testDecorationWithServiceAndAliasedInterface() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php index 0f8fff4eed26f..ba979be80bc04 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php @@ -463,7 +463,7 @@ public static function getSubscribedServices(): array 'autowired.nullable' => new ServiceClosureArgument(new Reference('service.id', ContainerInterface::NULL_ON_INVALID_REFERENCE)), 'autowired.parameter' => new ServiceClosureArgument('foobar'), 'map.decorated' => new ServiceClosureArgument(new Reference('.service_locator.oZHAdom.inner', ContainerInterface::NULL_ON_INVALID_REFERENCE)), - 'target' => new ServiceClosureArgument(new TypedReference('stdClass', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'someTarget')), + 'target' => new ServiceClosureArgument(new TypedReference('stdClass', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'someTarget', [new Target('someTarget')])), ]; $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); } From 87cf70a712dddadf3b081e00e297edc4a5bb668e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 28 Dec 2022 11:53:18 +0100 Subject: [PATCH 090/475] [DependencyInjection] Cut compilation time --- .../Compiler/InlineServiceDefinitionsPass.php | 14 +++++++++----- .../DependencyInjection/ContainerBuilder.php | 19 +++++++++++++++++-- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index 0fff617508791..951f2542304b2 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -51,6 +51,7 @@ public function process(ContainerBuilder $container) $analyzedContainer = $container; } try { + $notInlinableIds = []; $remainingInlinedIds = []; $this->connectedIds = $this->notInlinedIds = $container->getDefinitions(); do { @@ -60,7 +61,8 @@ public function process(ContainerBuilder $container) } $this->graph = $analyzedContainer->getCompiler()->getServiceReferenceGraph(); $notInlinedIds = $this->notInlinedIds; - $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = []; + $notInlinableIds += $this->notInlinableIds; + $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = $this->notInlinableIds = []; foreach ($analyzedContainer->getDefinitions() as $id => $definition) { if (!$this->graph->hasNode($id)) { @@ -86,7 +88,7 @@ public function process(ContainerBuilder $container) } while ($this->inlinedIds && $this->analyzingPass); foreach ($remainingInlinedIds as $id) { - if (isset($this->notInlinableIds[$id])) { + if (isset($notInlinableIds[$id])) { continue; } @@ -126,8 +128,10 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed $definition = $this->container->getDefinition($id); - if (!$this->isInlineableDefinition($id, $definition)) { - $this->notInlinableIds[$id] = true; + if (isset($this->notInlinableIds[$id]) || !$this->isInlineableDefinition($id, $definition)) { + if ($this->currentId !== $id) { + $this->notInlinableIds[$id] = true; + } return $value; } @@ -188,7 +192,7 @@ private function isInlineableDefinition(string $id, Definition $definition): boo return true; } - if ($this->currentId == $id) { + if ($this->currentId === $id) { return false; } $this->connectedIds[$id] = true; diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index ae7ca22ee5035..63e288adc63b3 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -114,6 +114,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ private array $vendors; + /** + * @var string[] the list of paths in vendor directories + */ + private array $pathsInVendor = []; + /** * @var array */ @@ -1610,17 +1615,27 @@ private function getExpressionLanguage(): ExpressionLanguage private function inVendors(string $path): bool { + $path = is_file($path) ? \dirname($path) : $path; + + if (isset($this->pathsInVendor[$path])) { + return $this->pathsInVendor[$path]; + } + $this->vendors ??= (new ComposerResource())->getVendors(); $path = realpath($path) ?: $path; + if (isset($this->pathsInVendor[$path])) { + return $this->pathsInVendor[$path]; + } + foreach ($this->vendors as $vendor) { if (str_starts_with($path, $vendor) && false !== strpbrk(substr($path, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { $this->addResource(new FileResource($vendor.'/composer/installed.json')); - return true; + return $this->pathsInVendor[$path] = true; } } - return false; + return $this->pathsInVendor[$path] = false; } } From a8f8903a94b2ea8f29915eafd324ea0da5a4f79c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 19 Dec 2022 16:20:10 +0100 Subject: [PATCH 091/475] [DependencyInjection] Stop considering empty env vars as populated --- .../DependencyInjection/EnvVarProcessor.php | 18 ++++++++---------- .../Tests/EnvVarProcessorTest.php | 9 +++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index c1787edfd7d57..44b8a312e2994 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -145,18 +145,16 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed if (false !== $i || 'string' !== $prefix) { $env = $getEnv($name); - } elseif (isset($_ENV[$name])) { - $env = $_ENV[$name]; - } elseif (isset($_SERVER[$name]) && !str_starts_with($name, 'HTTP_')) { - $env = $_SERVER[$name]; - } elseif (false === ($env = getenv($name)) || null === $env) { // null is a possible value because of thread safety issues + } elseif ('' === ($env = $_ENV[$name] ?? (str_starts_with($name, 'HTTP_') ? null : ($_SERVER[$name] ?? null))) + || false === ($env = $env ?? getenv($name) ?? false) // null is a possible value because of thread safety issues + ) { foreach ($this->loadedVars as $vars) { - if (false !== $env = ($vars[$name] ?? false)) { + if (false !== ($env = ($vars[$name] ?? false)) && '' !== $env) { break; } } - if (false === $env || null === $env) { + if (false === $env || '' === $env) { $loaders = $this->loaders; $this->loaders = new \ArrayIterator(); @@ -169,7 +167,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed continue; } $this->loadedVars[] = $vars = $loader->loadEnvVars(); - if (false !== $env = $vars[$name] ?? false) { + if (false !== ($env = ($vars[$name] ?? false)) && '' !== $env) { $ended = false; break; } @@ -184,7 +182,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed } } - if (false === $env || null === $env) { + if (false === $env) { if (!$this->container->hasParameter("env($name)")) { throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name)); } @@ -218,7 +216,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed if (\in_array($prefix, ['bool', 'not'], true)) { $env = (bool) (filter_var($env, \FILTER_VALIDATE_BOOL) ?: filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)); - return 'not' === $prefix ? !$env : $env; + return 'not' === $prefix xor $env; } if ('int' === $prefix) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php index 0ea6aa5679096..3b663e6594934 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php @@ -744,12 +744,15 @@ public function validCsv() public function testEnvLoader() { + $_ENV['BAZ_ENV_LOADER'] = ''; + $loaders = function () { yield new class() implements EnvVarLoaderInterface { public function loadEnvVars(): array { return [ 'FOO_ENV_LOADER' => '123', + 'BAZ_ENV_LOADER' => '', ]; } }; @@ -760,6 +763,7 @@ public function loadEnvVars(): array return [ 'FOO_ENV_LOADER' => '234', 'BAR_ENV_LOADER' => '456', + 'BAZ_ENV_LOADER' => '567', ]; } }; @@ -773,8 +777,13 @@ public function loadEnvVars(): array $result = $processor->getEnv('string', 'BAR_ENV_LOADER', function () {}); $this->assertSame('456', $result); + $result = $processor->getEnv('string', 'BAZ_ENV_LOADER', function () {}); + $this->assertSame('567', $result); + $result = $processor->getEnv('string', 'FOO_ENV_LOADER', function () {}); $this->assertSame('123', $result); // check twice + + unset($_ENV['BAZ_ENV_LOADER']); } public function testCircularEnvLoader() From 9f5c00567537d2b6ba7265a9c74af85aecd44329 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 28 Dec 2022 14:45:14 +0100 Subject: [PATCH 092/475] [PhpUnitBridge] Revert "minor #48725 Remove calls to `AnnotationRegistry::registerLoader()`" --- composer.json | 2 +- .../Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php | 9 +++++++++ src/Symfony/Bridge/PhpUnit/bootstrap.php | 9 +++++++++ src/Symfony/Bridge/PhpUnit/composer.json | 1 - 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index f789f4458df03..48111c1e0839a 100644 --- a/composer.json +++ b/composer.json @@ -128,7 +128,7 @@ "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.12", + "doctrine/orm": "^2.7.4", "egulias/email-validator": "^2.1.10|^3.1", "guzzlehttp/promises": "^1.4", "league/html-to-markdown": "^5.0", diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index b97a11d614929..015bc881ae573 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\PhpUnit\Legacy; +use Doctrine\Common\Annotations\AnnotationRegistry; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\RiskyTestError; use PHPUnit\Framework\TestCase; @@ -129,6 +130,14 @@ public function startTestSuite($suite) echo "Testing $suiteName\n"; $this->state = 0; + if (!class_exists(AnnotationRegistry::class, false) && class_exists(AnnotationRegistry::class)) { + if (method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { + AnnotationRegistry::registerUniqueLoader('class_exists'); + } elseif (method_exists(AnnotationRegistry::class, 'registerLoader')) { + AnnotationRegistry::registerLoader('class_exists'); + } + } + if ($this->skippedFile = getenv('SYMFONY_PHPUNIT_SKIPPED_TESTS')) { $this->state = 1; diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php index c81c828770dbc..f2064368f41a3 100644 --- a/src/Symfony/Bridge/PhpUnit/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php @@ -9,6 +9,7 @@ * file that was distributed with this source code. */ +use Doctrine\Common\Annotations\AnnotationRegistry; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler; // Detect if we need to serialize deprecations to a file. @@ -26,6 +27,14 @@ // Enforce a consistent locale setlocale(\LC_ALL, 'C'); +if (!class_exists(AnnotationRegistry::class, false) && class_exists(AnnotationRegistry::class)) { + if (method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { + AnnotationRegistry::registerUniqueLoader('class_exists'); + } elseif (method_exists(AnnotationRegistry::class, 'registerLoader')) { + AnnotationRegistry::registerLoader('class_exists'); + } +} + if ('disabled' !== getenv('SYMFONY_DEPRECATIONS_HELPER')) { DeprecationErrorHandler::register(getenv('SYMFONY_DEPRECATIONS_HELPER')); } diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index d576fb99986ff..b0ccda04315f1 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -28,7 +28,6 @@ "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" }, "conflict": { - "doctrine/annotations": "<1.10", "phpunit/phpunit": "<7.5|9.1.2" }, "autoload": { From a8bbf632df06b26bbd8a62f22203054bbff34d32 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 28 Dec 2022 15:32:23 +0100 Subject: [PATCH 093/475] Fix merge --- .../Fixtures/php/services_almost_circular_private.php | 2 +- .../Fixtures/php/services_almost_circular_public.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index 317d153cb601b..d9cd489a02b60 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -495,7 +495,7 @@ protected static function getBar6Service($container) */ protected static function getDoctrine_ListenerService($container) { - $a = ($container->services['doctrine.entity_manager'] ?? $container->getDoctrine_EntityManagerService()); + $a = ($container->services['doctrine.entity_manager'] ?? self::getDoctrine_EntityManagerService()); if (isset($container->privates['doctrine.listener'])) { return $container->privates['doctrine.listener']; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index 529d5263f9407..385bcb3684491 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -289,7 +289,7 @@ protected static function getDoctrine_EntityListenerResolverService($container) */ protected static function getDoctrine_EntityManagerService($container) { - $a = ($container->services['doctrine.entity_listener_resolver'] ?? $container->getDoctrine_EntityListenerResolverService()); + $a = ($container->services['doctrine.entity_listener_resolver'] ?? self::getDoctrine_EntityListenerResolverService()); if (isset($container->services['doctrine.entity_manager'])) { return $container->services['doctrine.entity_manager']; @@ -308,7 +308,7 @@ protected static function getDoctrine_EntityManagerService($container) */ protected static function getDoctrine_ListenerService($container) { - $a = ($container->services['doctrine.entity_manager'] ?? $container->getDoctrine_EntityManagerService()); + $a = ($container->services['doctrine.entity_manager'] ?? self::getDoctrine_EntityManagerService()); if (isset($container->services['doctrine.listener'])) { return $container->services['doctrine.listener']; @@ -510,7 +510,7 @@ protected static function getLoggerService($container) */ protected static function getMailer_TransportService($container) { - $a = ($container->services['mailer.transport_factory'] ?? $container->getMailer_TransportFactoryService()); + $a = ($container->services['mailer.transport_factory'] ?? self::getMailer_TransportFactoryService()); if (isset($container->services['mailer.transport'])) { return $container->services['mailer.transport']; @@ -543,7 +543,7 @@ protected static function getMailer_TransportFactoryService($container) */ protected static function getMailer_TransportFactory_AmazonService($container) { - $a = ($container->services['monolog.logger_2'] ?? $container->getMonolog_Logger2Service()); + $a = ($container->services['monolog.logger_2'] ?? self::getMonolog_Logger2Service()); if (isset($container->services['mailer.transport_factory.amazon'])) { return $container->services['mailer.transport_factory.amazon']; From 8671ad52f1ec24baa4b96aabed2bf0d54e74db81 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 28 Dec 2022 15:47:09 +0100 Subject: [PATCH 094/475] Drop v1 contracts packages everywhere --- src/Symfony/Bridge/Doctrine/composer.json | 4 ++-- src/Symfony/Bridge/Monolog/composer.json | 2 +- src/Symfony/Bridge/PhpUnit/composer.json | 2 +- src/Symfony/Bridge/ProxyManager/composer.json | 2 +- src/Symfony/Bridge/Twig/composer.json | 2 +- src/Symfony/Bundle/FrameworkBundle/composer.json | 2 +- src/Symfony/Component/Cache/composer.json | 4 ++-- src/Symfony/Component/Config/composer.json | 7 ++++--- src/Symfony/Component/Console/composer.json | 4 ++-- .../Tests/Compiler/AutowirePassTest.php | 5 ----- .../Tests/Compiler/AutowireRequiredMethodsPassTest.php | 9 --------- .../Compiler/AutowireRequiredPropertiesPassTest.php | 5 ----- src/Symfony/Component/DependencyInjection/composer.json | 4 ++-- src/Symfony/Component/ErrorHandler/composer.json | 5 ++++- src/Symfony/Component/EventDispatcher/composer.json | 7 ++++--- src/Symfony/Component/ExpressionLanguage/composer.json | 2 +- src/Symfony/Component/Form/composer.json | 6 +++--- .../Component/HttpClient/Tests/HttpClientTestCase.php | 4 ---- src/Symfony/Component/HttpClient/composer.json | 4 ++-- src/Symfony/Component/HttpFoundation/composer.json | 2 +- src/Symfony/Component/HttpKernel/composer.json | 8 +++++--- src/Symfony/Component/Ldap/composer.json | 2 +- .../Component/Mailer/Bridge/OhMySmtp/composer.json | 2 +- src/Symfony/Component/Mailer/composer.json | 5 +++-- .../Component/Messenger/Bridge/AmazonSqs/composer.json | 7 +++++-- .../Component/Messenger/Bridge/Doctrine/composer.json | 2 +- src/Symfony/Component/Messenger/composer.json | 4 ++-- .../Component/Notifier/Bridge/Mercure/composer.json | 2 +- src/Symfony/Component/Notifier/composer.json | 6 ++++-- src/Symfony/Component/OptionsResolver/composer.json | 2 +- src/Symfony/Component/PropertyAccess/composer.json | 2 +- src/Symfony/Component/Security/Core/composer.json | 4 ++-- src/Symfony/Component/Security/Http/composer.json | 2 +- src/Symfony/Component/Stopwatch/composer.json | 2 +- src/Symfony/Component/String/composer.json | 4 ++-- src/Symfony/Component/Translation/composer.json | 8 +++++--- src/Symfony/Component/Validator/composer.json | 4 ++-- 37 files changed, 70 insertions(+), 78 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index bee7d29c5b1b6..9d16c12e3273d 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -19,10 +19,10 @@ "php": ">=8.1", "doctrine/event-manager": "^1.2|^2", "doctrine/persistence": "^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "require-dev": { "symfony/stopwatch": "^5.4|^6.0", diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index 025d54a48398d..8fc06d8ece4e8 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=8.1", "monolog/monolog": "^1.25.1|^2|^3", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/http-kernel": "^5.4|^6.0" }, "require-dev": { diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index b0ccda04315f1..85b11227d01c0 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -21,7 +21,7 @@ "php": ">=7.1.3" }, "require-dev": { - "symfony/deprecation-contracts": "^2.1|^3.0", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/error-handler": "^5.4|^6.0" }, "suggest": { diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index b3e1e9a9b5e75..e7386931204c4 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -19,7 +19,7 @@ "php": ">=8.1", "friendsofphp/proxy-manager-lts": "^1.0.2", "symfony/dependency-injection": "^6.3", - "symfony/deprecation-contracts": "^2.1|^3" + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { "symfony/config": "^6.1" diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index afcd87d37096d..26b04afa63702 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^2.13|^3.0.4" }, "require-dev": { diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index d5391abcd6567..096172bcf80e8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -22,7 +22,7 @@ "symfony/cache": "^5.4|^6.0", "symfony/config": "^6.1", "symfony/dependency-injection": "^6.2", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.1", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/http-foundation": "^6.2", diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index 8a11d7e46a21a..db55ad279214d 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -24,8 +24,8 @@ "php": ">=8.1", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2|^3", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/var-exporter": "^6.2" }, "require-dev": { diff --git a/src/Symfony/Component/Config/composer.json b/src/Symfony/Component/Config/composer.json index 85d842a533162..9896bcc4d5d1b 100644 --- a/src/Symfony/Component/Config/composer.json +++ b/src/Symfony/Component/Config/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/filesystem": "^5.4|^6.0", "symfony/polyfill-ctype": "~1.8" }, @@ -25,11 +25,12 @@ "symfony/event-dispatcher": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", "symfony/messenger": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/yaml": "^5.4|^6.0" }, "conflict": { - "symfony/finder": "<5.4" + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" diff --git a/src/Symfony/Component/Console/composer.json b/src/Symfony/Component/Console/composer.json index bafe5d16511f4..06f0d6518835d 100644 --- a/src/Symfony/Component/Console/composer.json +++ b/src/Symfony/Component/Console/composer.json @@ -17,9 +17,9 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/string": "^5.4|^6.0" }, "require-dev": { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 8224150357dda..7f29757b50d00 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -34,7 +34,6 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\WithTarget; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\ExpressionLanguage\Expression; -use Symfony\Contracts\Service\Attribute\Required; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; @@ -705,10 +704,6 @@ public function testSetterInjection() public function testSetterInjectionWithAttribute() { - if (!class_exists(Required::class)) { - $this->markTestSkipped('symfony/service-contracts 2.2 required'); - } - $container = new ContainerBuilder(); $container->register(Foo::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php index 9a7cdf7bc0c30..9cedd5e02f249 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php @@ -16,7 +16,6 @@ use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Tests\Fixtures\WitherStaticReturnType; -use Symfony\Contracts\Service\Attribute\Required; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; @@ -57,10 +56,6 @@ public function testSetterInjection() public function testSetterInjectionWithAttribute() { - if (!class_exists(Required::class)) { - $this->markTestSkipped('symfony/service-contracts 2.2 required'); - } - $container = new ContainerBuilder(); $container->register(Foo::class); @@ -145,10 +140,6 @@ public function testWitherWithStaticReturnTypeInjection() public function testWitherInjectionWithAttribute() { - if (!class_exists(Required::class)) { - $this->markTestSkipped('symfony/service-contracts 2.2 required'); - } - $container = new ContainerBuilder(); $container->register(Foo::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php index a9f7388061693..2cee2e7ed5c35 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredPropertiesPassTest.php @@ -15,7 +15,6 @@ use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredPropertiesPass; use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Contracts\Service\Attribute\Required; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; require_once __DIR__.'/../Fixtures/includes/autowiring_classes_74.php'; @@ -41,10 +40,6 @@ public function testInjection() public function testAttribute() { - if (!class_exists(Required::class)) { - $this->markTestSkipped('symfony/service-contracts 2.2 required'); - } - $container = new ContainerBuilder(); $container->register(Foo::class); diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index 307a78fa1c33e..a71ed09d27f80 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -18,8 +18,8 @@ "require": { "php": ">=8.1", "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/service-contracts": "^1.1.6|^2.0|^3.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", "symfony/var-exporter": "^6.2" }, "require-dev": { diff --git a/src/Symfony/Component/ErrorHandler/composer.json b/src/Symfony/Component/ErrorHandler/composer.json index 5331dd88366c6..03eec618df8fb 100644 --- a/src/Symfony/Component/ErrorHandler/composer.json +++ b/src/Symfony/Component/ErrorHandler/composer.json @@ -23,7 +23,10 @@ "require-dev": { "symfony/http-kernel": "^5.4|^6.0", "symfony/serializer": "^5.4|^6.0", - "symfony/deprecation-contracts": "^2.1|^3" + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" }, "autoload": { "psr-4": { "Symfony\\Component\\ErrorHandler\\": "" }, diff --git a/src/Symfony/Component/EventDispatcher/composer.json b/src/Symfony/Component/EventDispatcher/composer.json index c05373f331c1a..925a92028a0ef 100644 --- a/src/Symfony/Component/EventDispatcher/composer.json +++ b/src/Symfony/Component/EventDispatcher/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^2|^3" + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "require-dev": { "symfony/dependency-injection": "^5.4|^6.0", @@ -25,12 +25,13 @@ "symfony/config": "^5.4|^6.0", "symfony/error-handler": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/stopwatch": "^5.4|^6.0", "psr/log": "^1|^2|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4" + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", diff --git a/src/Symfony/Component/ExpressionLanguage/composer.json b/src/Symfony/Component/ExpressionLanguage/composer.json index 3c8811b0e3a99..a7688ad4601cf 100644 --- a/src/Symfony/Component/ExpressionLanguage/composer.json +++ b/src/Symfony/Component/ExpressionLanguage/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=8.1", "symfony/cache": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "autoload": { "psr-4": { "Symfony\\Component\\ExpressionLanguage\\": "" }, diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index c740948c5ec08..e859bf2c7fd62 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -17,14 +17,14 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/options-resolver": "^5.4|^6.0", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-icu": "^1.21", "symfony/polyfill-mbstring": "~1.0", "symfony/property-access": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "require-dev": { "doctrine/collections": "^1.0|^2.0", @@ -52,7 +52,7 @@ "symfony/framework-bundle": "<5.4", "symfony/http-kernel": "<5.4", "symfony/translation": "<5.4", - "symfony/translation-contracts": "<1.1.7", + "symfony/translation-contracts": "<2.5", "symfony/twig-bridge": "<5.4" }, "suggest": { diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php index 1f85f4ed143ce..fd92865ca70c0 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php @@ -321,10 +321,6 @@ private static function startVulcain(HttpClientInterface $client) throw new SkippedTestSuiteError('Testing with the "vulcain" is not supported on Windows.'); } - if (['application/json'] !== $client->request('GET', 'http://127.0.0.1:8057/json')->getHeaders()['content-type']) { - throw new SkippedTestSuiteError('symfony/http-client-contracts >= 2.0.1 required'); - } - $process = new Process(['vulcain'], null, [ 'DEBUG' => 1, 'UPSTREAM' => 'http://127.0.0.1:8057', diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index 61be65b74ecc4..f4d175f4b46f6 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -23,9 +23,9 @@ "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "^3", - "symfony/service-contracts": "^1.0|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "require-dev": { "amphp/amp": "^2.5", diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json index 2023300b8cc2d..944029655a181 100644 --- a/src/Symfony/Component/HttpFoundation/composer.json +++ b/src/Symfony/Component/HttpFoundation/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.1", "symfony/polyfill-php83": "^1.27" }, diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 41ef39d3ae749..8193edbe8c28c 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.1", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", @@ -34,12 +34,12 @@ "symfony/dom-crawler": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/http-client-contracts": "^2.5|^3", "symfony/process": "^5.4|^6.0 10000 ", "symfony/routing": "^5.4|^6.0", "symfony/stopwatch": "^5.4|^6.0", "symfony/translation": "^5.4|^6.0", - "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^5.4|^6.0", "psr/cache": "^1.0|^2.0|^3.0", "twig/twig": "^2.13|^3.0.4" @@ -56,9 +56,11 @@ "symfony/dependency-injection": "<6.2", "symfony/doctrine-bridge": "<5.4", "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", "symfony/mailer": "<5.4", "symfony/messenger": "<5.4", "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", "symfony/twig-bridge": "<5.4", "symfony/validator": "<5.4", "twig/twig": "<2.13" diff --git a/src/Symfony/Component/Ldap/composer.json b/src/Symfony/Component/Ldap/composer.json index a2ab982ea63f8..40e775686b78b 100644 --- a/src/Symfony/Component/Ldap/composer.json +++ b/src/Symfony/Component/Ldap/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=8.1", "ext-ldap": "*", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/options-resolver": "^5.4|^6.0" }, "require-dev": { diff --git a/src/Symfony/Component/Mailer/Bridge/OhMySmtp/composer.json b/src/Symfony/Component/Mailer/Bridge/OhMySmtp/composer.json index d77ee18bdeed3..b984097a5bd5c 100644 --- a/src/Symfony/Component/Mailer/Bridge/OhMySmtp/composer.json +++ b/src/Symfony/Component/Mailer/Bridge/OhMySmtp/composer.json @@ -22,7 +22,7 @@ "require": { "php": ">=8.1", "psr/event-dispatcher": "^1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/mailer": "^5.4|^6.0" }, "require-dev": { diff --git a/src/Symfony/Component/Mailer/composer.json b/src/Symfony/Component/Mailer/composer.json index e83a4aade2ee9..e98f9e03925b4 100644 --- a/src/Symfony/Component/Mailer/composer.json +++ b/src/Symfony/Component/Mailer/composer.json @@ -22,15 +22,16 @@ "psr/log": "^1|^2|^3", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/mime": "^6.2", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "require-dev": { "symfony/console": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/http-client-contracts": "^2.5|^3", "symfony/messenger": "^6.2", "symfony/twig-bridge": "^6.2" }, "conflict": { + "symfony/http-client-contracts": "<2.5", "symfony/http-kernel": "<5.4", "symfony/messenger": "<6.2", "symfony/mime": "<6.2", diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json index df8b9ad8df506..090a37b6a82b0 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/composer.json @@ -20,14 +20,17 @@ "async-aws/core": "^1.5", "async-aws/sqs": "^1.0", "symfony/messenger": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "psr/log": "^1|^2|^3" }, "require-dev": { - "symfony/http-client-contracts": "^1|^2|^3", + "symfony/http-client-contracts": "^2.5|^3", "symfony/property-access": "^5.4|^6.0", "symfony/serializer": "^5.4|^6.0" }, + "conflict": { + "symfony/http-client-contracts": "<2.5" + }, "autoload": { "psr-4": { "Symfony\\Component\\Messenger\\Bridge\\AmazonSqs\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json index 5f377c81a3628..ba116bdba0801 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json @@ -19,7 +19,7 @@ "php": ">=8.1", "doctrine/dbal": "^2.13|^3.0", "symfony/messenger": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "require-dev": { "doctrine/persistence": "^1.3|^2|^3", diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json index 63610781b4e55..8d3d0f2653ac7 100644 --- a/src/Symfony/Component/Messenger/composer.json +++ b/src/Symfony/Component/Messenger/composer.json @@ -30,13 +30,13 @@ "symfony/rate-limiter": "^5.4|^6.0", "symfony/routing": "^5.4|^6.0", "symfony/serializer": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/stopwatch": "^5.4|^6.0", "symfony/validator": "^5.4|^6.0" }, "conflict": { "symfony/event-dispatcher": "<5.4", - "symfony/event-dispatcher-contracts": "<2", + "symfony/event-dispatcher-contracts": "<2.5", "symfony/framework-bundle": "<5.4", "symfony/http-kernel": "<5.4", "symfony/serializer": "<5.4" diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json b/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json index 60997223e03c8..51f63fcfa4b2b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json @@ -19,7 +19,7 @@ "php": ">=8.1", "symfony/mercure": "^0.5.2|^0.6", "symfony/notifier": "^6.2", - "symfony/service-contracts": "^1.10|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Mercure\\": "" }, diff --git a/src/Symfony/Component/Notifier/composer.json b/src/Symfony/Component/Notifier/composer.json index cceb46806f1ca..c2480d32f2b9e 100644 --- a/src/Symfony/Component/Notifier/composer.json +++ b/src/Symfony/Component/Notifier/composer.json @@ -20,13 +20,15 @@ "psr/log": "^1|^2|^3" }, "require-dev": { - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/http-client-contracts": "^2|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^2.5|^3", "symfony/http-foundation": "^5.4|^6.0", "symfony/messenger": "^5.4|^6.0" }, "conflict": { "symfony/event-dispatcher": "<5.4", + "symfony/event-dispatcher-contracts": "<2.5", + "symfony/http-client-contracts": "<2.5", "symfony/http-kernel": "<5.4" }, "autoload": { diff --git a/src/Symfony/Component/OptionsResolver/composer.json b/src/Symfony/Component/OptionsResolver/composer.json index 3355b24efc6f3..9f2daf4e7bf74 100644 --- a/src/Symfony/Component/OptionsResolver/composer.json +++ b/src/Symfony/Component/OptionsResolver/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3" + "symfony/deprecation-contracts": "^2.5|^3" }, "autoload": { "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" }, diff --git a/src/Symfony/Component/PropertyAccess/composer.json b/src/Symfony/Component/PropertyAccess/composer.json index a8ba0b159dc2b..324eb0c1e74b4 100644 --- a/src/Symfony/Component/PropertyAccess/composer.json +++ b/src/Symfony/Component/PropertyAccess/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/property-info": "^5.4|^6.0" }, "require-dev": { diff --git a/src/Symfony/Component/Security/Core/composer.json b/src/Symfony/Component/Security/Core/composer.json index 7e5f8679cd995..5f933534b29ce 100644 --- a/src/Symfony/Component/Security/Core/composer.json +++ b/src/Symfony/Component/Security/Core/composer.json @@ -17,8 +17,8 @@ ], "require": { "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^1.1|^2|^3", - "symfony/service-contracts": "^1.1.6|^2|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/password-hasher": "^5.4|^6.0" }, "require-dev": { diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 66876a3e38e4b..061c52631e5fd 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/security-core": "^6.0", "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^6.2", diff --git a/src/Symfony/Component/Stopwatch/composer.json b/src/Symfony/Component/Stopwatch/composer.json index da4c3d2e32f17..4aa02b5f343d2 100644 --- a/src/Symfony/Component/Stopwatch/composer.json +++ b/src/Symfony/Component/Stopwatch/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/service-contracts": "^1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "autoload": { "psr-4": { "Symfony\\Component\\Stopwatch\\": "" }, diff --git a/src/Symfony/Component/String/composer.json b/src/Symfony/Component/String/composer.json index 44a809d589d6f..3545c85311b7b 100644 --- a/src/Symfony/Component/String/composer.json +++ b/src/Symfony/Component/String/composer.json @@ -26,11 +26,11 @@ "symfony/error-handler": "^5.4|^6.0", "symfony/intl": "^6.2", "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", + "symfony/translation-contracts": "^2.5|^3.0", "symfony/var-exporter": "^5.4|^6.0" }, "conflict": { - "symfony/translation-contracts": "<2.0" + "symfony/translation-contracts": "<2.5" }, "autoload": { "psr-4": { "Symfony\\Component\\String\\": "" }, diff --git a/src/Symfony/Component/Translation/composer.json b/src/Symfony/Component/Translation/composer.json index d75df651b0bc4..9b22806bd7d7f 100644 --- a/src/Symfony/Component/Translation/composer.json +++ b/src/Symfony/Component/Translation/composer.json @@ -18,19 +18,19 @@ "require": { "php": ">=8.1", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.3|^3.0" + "symfony/translation-contracts": "^2.5|^3.0" }, "require-dev": { "nikic/php-parser": "^4.13", "symfony/config": "^5.4|^6.0", "symfony/console": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-client-contracts": "^2.5|^3.0", "symfony/http-kernel": "^5.4|^6.0", "symfony/intl": "^5.4|^6.0", "symfony/polyfill-intl-icu": "^1.21", "symfony/routing": "^5.4|^6.0", - "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/yaml": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", "psr/log": "^1|^2|^3" @@ -38,7 +38,9 @@ "conflict": { "symfony/config": "<5.4", "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", "symfony/twig-bundle": "<5.4", "symfony/yaml": "<5.4", "symfony/console": "<5.4" diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index c68ad9cf534b6..d23d38f861dcc 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -17,11 +17,11 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php83": "^1.27", - "symfony/translation-contracts": "^1.1|^2|^3" + "symfony/translation-contracts": "^2.5|^3" }, "require-dev": { "symfony/console": "^5.4|^6.0", From 6d3b3f161bf8e84c8730220eeb70c3af2f121f93 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 28 Dec 2022 16:23:52 +0100 Subject: [PATCH 095/475] Fix revert --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 48111c1e0839a..f789f4458df03 100644 --- a/composer.json +++ b/composer.json @@ -128,7 +128,7 @@ "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.7.4", + "doctrine/orm": "^2.12", "egulias/email-validator": "^2.1.10|^3.1", "guzzlehttp/promises": "^1.4", "league/html-to-markdown": "^5.0", From 49891944fcb9a4092dac1abeed0801fa396a4421 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 28 Dec 2022 16:29:29 +0100 Subject: [PATCH 096/475] Fix merge --- .../Fixtures/php/services_almost_circular_private.php | 2 +- .../Fixtures/php/services_almost_circular_public.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index d9cd489a02b60..705f789404677 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -495,7 +495,7 @@ protected static function getBar6Service($container) */ protected static function getDoctrine_ListenerService($container) { - $a = ($container->services['doctrine.entity_manager'] ?? self::getDoctrine_EntityManagerService()); + $a = ($container->services['doctrine.entity_manager'] ?? self::getDoctrine_EntityManagerService($container)); if (isset($container->privates['doctrine.listener'])) { return $container->privates['doctrine.listener']; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index 385bcb3684491..50081e7e4617a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -289,7 +289,7 @@ protected static function getDoctrine_EntityListenerResolverService($container) */ protected static function getDoctrine_EntityManagerService($container) { - $a = ($container->services['doctrine.entity_listener_resolver'] ?? self::getDoctrine_EntityListenerResolverService()); + $a = ($container->services['doctrine.entity_listener_resolver'] ?? self::getDoctrine_EntityListenerResolverService($container)); if (isset($container->services['doctrine.entity_manager'])) { return $container->services['doctrine.entity_manager']; @@ -308,7 +308,7 @@ protected static function getDoctrine_EntityManagerService($container) */ protected static function getDoctrine_ListenerService($container) { - $a = ($container->services['doctrine.entity_manager'] ?? self::getDoctrine_EntityManagerService()); + $a = ($container->services['doctrine.entity_manager'] ?? self::getDoctrine_EntityManagerService($container)); if (isset($container->services['doctrine.listener'])) { return $container->services['doctrine.listener']; @@ -510,7 +510,7 @@ protected static function getLoggerService($container) */ protected static function getMailer_TransportService($container) { - $a = ($container->services['mailer.transport_factory'] ?? self::getMailer_TransportFactoryService()); + $a = ($container->services['mailer.transport_factory'] ?? self::getMailer_TransportFactoryService($container)); if (isset($container->services['mailer.transport'])) { return $container->services['mailer.transport']; @@ -543,7 +543,7 @@ protected static function getMailer_TransportFactoryService($container) */ protected static function getMailer_TransportFactory_AmazonService($container) { - $a = ($container->services['monolog.logger_2'] ?? self::getMonolog_Logger2Service()); + $a = ($container->services['monolog.logger_2'] ?? self::getMonolog_Logger2Service($container)); if (isset($container->services['mailer.transport_factory.amazon'])) { return $container->services['mailer.transport_factory.amazon']; From 1cadd46e7ff3ff3d4d5f8374d16413420125c414 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Fri, 23 Dec 2022 14:14:43 +0100 Subject: [PATCH 097/475] [DependencyInjection] Auto exclude referencing service in `TaggedIteratorArgument` --- .../Argument/TaggedIteratorArgument.php | 10 +++++- .../Attribute/TaggedIterator.php | 1 + .../Attribute/TaggedLocator.php | 1 + .../DependencyInjection/CHANGELOG.md | 2 ++ .../Compiler/AutowirePass.php | 4 +-- .../Compiler/PriorityTaggedServiceTrait.php | 5 ++- .../ResolveTaggedIteratorArgumentPass.php | 7 +++- .../Loader/XmlFileLoader.php | 2 +- .../Loader/YamlFileLoader.php | 4 +-- .../schema/dic/services/services-1.0.xsd | 1 + .../RegisterServiceSubscribersPassTest.php | 2 +- .../ResolveTaggedIteratorArgumentPassTest.php | 32 +++++++++++++++++++ 12 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php b/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php index c33e8615b254e..bfe9787f7c9bd 100644 --- a/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php +++ b/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php @@ -24,6 +24,7 @@ class TaggedIteratorArgument extends IteratorArgument private ?string $defaultPriorityMethod; private bool $needsIndexes; private array $exclude; + private bool $excludeSelf = true; /** * @param string $tag The name of the tag identifying the target services @@ -32,8 +33,9 @@ class TaggedIteratorArgument extends IteratorArgument * @param bool $needsIndexes Whether indexes are required and should be generated when computing the map * @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute * @param array $exclude Services to exclude from the iterator + * @param bool $excludeSelf Whether to automatically exclude the referencing service from the iterator */ - public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null, array $exclude = []) + public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null, array $exclude = [], bool $excludeSelf = true) { parent::__construct([]); @@ -47,6 +49,7 @@ public function __construct(string $tag, string $indexAttribute = null, string $ $this->needsIndexes = $needsIndexes; $this->defaultPriorityMethod = $defaultPriorityMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Priority' : null); $this->exclude = $exclude; + $this->excludeSelf = $excludeSelf; } public function getTag() @@ -78,4 +81,9 @@ public function getExclude(): array { return $this->exclude; } + + public function excludeSelf(): bool + { + return $this->excludeSelf; + } } diff --git a/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php b/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php index 5898a6afe0e81..fb33fb572942b 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php @@ -20,6 +20,7 @@ public function __construct( public ?string $defaultIndexMethod = null, public ?string $defaultPriorityMethod = null, public string|array $exclude = [], + public bool $excludeSelf = true, ) { } } diff --git a/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php b/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php index b706a6388bf0d..f05ae53bc4284 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php @@ -20,6 +20,7 @@ public function __construct( public ?string $defaultIndexMethod = null, public ?string $defaultPriorityMethod = null, public string|array $exclude = [], + public bool $excludeSelf = true, ) { } } diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 81dec56e681f3..df045b56f05c8 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -24,6 +24,8 @@ CHANGELOG * Deprecate using numeric parameter names * Add support for tagged iterators/locators `exclude` option to the xml and yaml loaders/dumpers * Allow injecting `string $env` into php config closures + * Add `excludeSelf` parameter to `TaggedIteratorArgument` with default value to `true` + to control whether the referencing service should be automatically excluded from the iterator 6.1 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 66a175d76c267..ac94cd7ae5b24 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -88,11 +88,11 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed } if ($value instanceof TaggedIterator) { - return new TaggedIteratorArgument($value->tag, $value->indexAttribute, $value->defaultIndexMethod, false, $value->defaultPriorityMethod, (array) $value->exclude); + return new TaggedIteratorArgument($value->tag, $value->indexAttribute, $value->defaultIndexMethod, false, $value->defaultPriorityMethod, (array) $value->exclude, $value->excludeSelf); } if ($value instanceof TaggedLocator) { - return new ServiceLocatorArgument(new TaggedIteratorArgument($value->tag, $value->indexAttribute, $value->defaultIndexMethod, true, $value->defaultPriorityMethod, (array) $value->exclude)); + return new ServiceLocatorArgument(new TaggedIteratorArgument($value->tag, $value->indexAttribute, $value->defaultIndexMethod, true, $value->defaultPriorityMethod, (array) $value->exclude, $value->excludeSelf)); } if ($value instanceof MapDecorated) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php index 309bf63118d4e..2ddcaa0c08d8c 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php @@ -37,9 +37,8 @@ trait PriorityTaggedServiceTrait * * @return Reference[] */ - private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagName, ContainerBuilder $container): array + private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagName, ContainerBuilder $container, array $exclude = []): array { - $exclude = []; $indexAttribute = $defaultIndexMethod = $needsIndexes = $defaultPriorityMethod = null; if ($tagName instanceof TaggedIteratorArgument) { @@ -47,7 +46,7 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam $defaultIndexMethod = $tagName->getDefaultIndexMethod(); $needsIndexes = $tagName->needsIndexes(); $defaultPriorityMethod = $tagName->getDefaultPriorityMethod() ?? 'getDefaultPriority'; - $exclude = $tagName->getExclude(); + $exclude = array_merge($exclude, $tagName->getExclude()); $tagName = $tagName->getTag(); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php index 1fca5ebaa5f8a..469d001b51fea 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php @@ -28,7 +28,12 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed return parent::processValue($value, $isRoot); } - $value->setValues($this->findAndSortTaggedServices($value, $this->container)); + $exclude = $value->getExclude(); + if ($value->excludeSelf()) { + $exclude[] = $this->currentId; + } + + $value->setValues($this->findAndSortTaggedServices($value, $this->container, $exclude)); return $value; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 7c0ea3261300b..4acbfe56a9aa3 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -550,7 +550,7 @@ private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file $excludes = [$arg->getAttribute('exclude')]; } - $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null, $excludes); + $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null, $excludes, $arg->getAttribute('exclude-self') ?: true); if ($forLocator) { $arguments[$key] = new ServiceLocatorArgument($arguments[$key]); diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index ad61c14437d1a..a9e35fdad654c 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -824,11 +824,11 @@ private function resolveServices(mixed $value, string $file, bool $isParameter = $forLocator = 'tagged_locator' === $value->getTag(); if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) { - if ($diff = array_diff(array_keys($argument), $supportedKeys = ['tag', 'index_by', 'default_index_method', 'default_priority_method', 'exclude'])) { + if ($diff = array_diff(array_keys($argument), $supportedKeys = ['tag', 'index_by', 'default_index_method', 'default_priority_method', 'exclude', 'exclude_self'])) { throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "%s".', $value->getTag(), implode('", "', $diff), implode('", "', $supportedKeys))); } - $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null, (array) ($argument['exclude'] ?? null)); + $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null, (array) ($argument['exclude'] ?? null), $argument['exclude_self'] ?? true); } elseif (\is_string($argument) && $argument) { $argument = new TaggedIteratorArgument($argument, null, null, $forLocator); } else { diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index 20e97866788b6..83e430a859445 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -302,6 +302,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php index ba979be80bc04..4da06e889b715 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php @@ -462,7 +462,7 @@ public static function getSubscribedServices(): array 'autowired' => new ServiceClosureArgument(new Reference('service.id')), 'autowired.nullable' => new ServiceClosureArgument(new Reference('service.id', ContainerInterface::NULL_ON_INVALID_REFERENCE)), 'autowired.parameter' => new ServiceClosureArgument('foobar'), - 'map.decorated' => new ServiceClosureArgument(new Reference('.service_locator.oZHAdom.inner', ContainerInterface::NULL_ON_INVALID_REFERENCE)), + 'map.decorated' => new ServiceClosureArgument(new Reference('.service_locator.LnJLtj2.inner', ContainerInterface::NULL_ON_INVALID_REFERENCE)), 'target' => new ServiceClosureArgument(new TypedReference('stdClass', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'someTarget', [new Target('someTarget')])), ]; $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTaggedIteratorArgumentPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTaggedIteratorArgumentPassTest.php index a62a585c6ef0c..7e2fa2f7ddda1 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTaggedIteratorArgumentPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTaggedIteratorArgumentPassTest.php @@ -54,4 +54,36 @@ public function testProcessWithIndexes() $expected->setValues(['1' => new TypedReference('service_a', 'stdClass'), '2' => new TypedReference('service_b', 'stdClass')]); $this->assertEquals($expected, $properties['foos']); } + + public function testProcesWithAutoExcludeReferencingService() + { + $container = new ContainerBuilder(); + $container->register('service_a', 'stdClass')->addTag('foo', ['key' => '1']); + $container->register('service_b', 'stdClass')->addTag('foo', ['key' => '2']); + $container->register('service_c', 'stdClass')->addTag('foo', ['key' => '3'])->setProperty('foos', new TaggedIteratorArgument('foo', 'key')); + + (new ResolveTaggedIteratorArgumentPass())->process($container); + + $properties = $container->getDefinition('service_c')->getProperties(); + + $expected = new TaggedIteratorArgument('foo', 'key'); + $expected->setValues(['1' => new TypedReference('service_a', 'stdClass'), '2' => new TypedReference('service_b', 'stdClass')]); + $this->assertEquals($expected, $properties['foos']); + } + + public function testProcesWithoutAutoExcludeReferencingService() + { + $container = new ContainerBuilder(); + $container->register('service_a', 'stdClass')->addTag('foo', ['key' => '1']); + $container->register('service_b', 'stdClass')->addTag('foo', ['key' => '2']); + $container->register('service_c', 'stdClass')->addTag('foo', ['key' => '3'])->setProperty('foos', new TaggedIteratorArgument(tag: 'foo', indexAttribute: 'key', excludeSelf: false)); + + (new ResolveTaggedIteratorArgumentPass())->process($container); + + $properties = $container->getDefinition('service_c')->getProperties(); + + $expected = new TaggedIteratorArgument(tag: 'foo', indexAttribute: 'key', excludeSelf: false); + $expected->setValues(['1' => new TypedReference('service_a', 'stdClass'), '2' => new TypedReference('service_b', 'stdClass'), '3' => new TypedReference('service_c', 'stdClass')]); + $this->assertEquals($expected, $properties['foos']); + } } From ecc53554fd54ba8ec47710d13913a77ddac9b8cb Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Mon, 24 Oct 2022 21:08:21 +0200 Subject: [PATCH 098/475] [HttpFoundation] Add `StreamedJsonResponse` for efficient JSON streaming --- .../Component/HttpFoundation/CHANGELOG.md | 1 + .../HttpFoundation/StreamedJsonResponse.php | 139 ++++++++++ .../Tests/StreamedJsonResponseTest.php | 241 ++++++++++++++++++ 3 files changed, 381 insertions(+) create mode 100644 src/Symfony/Component/HttpFoundation/StreamedJsonResponse.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/StreamedJsonResponseTest.php diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index ae0902547f8f0..1aaf6ed0f8507 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG 6.2 --- + * Add `StreamedJsonResponse` class for efficient JSON streaming * The HTTP cache store uses the `xxh128` algorithm * Deprecate calling `JsonResponse::setCallback()`, `Response::setExpires/setLastModified/setEtag()`, `MockArraySessionStorage/NativeSessionStorage::setMetadataBag()`, `NativeSessionStorage::setSaveHandler()` without arguments * Add request matchers under the `Symfony\Component\HttpFoundation\RequestMatcher` namespace diff --git a/src/Symfony/Component/HttpFoundation/StreamedJsonResponse.php b/src/Symfony/Component/HttpFoundation/StreamedJsonResponse.php new file mode 100644 index 0000000000000..445bd77d794cb --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/StreamedJsonResponse.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedJsonResponse represents a streamed HTTP response for JSON. + * + * A StreamedJsonResponse uses a structure and generics to create an + * efficient resource-saving JSON response. + * + * It is recommended to use flush() function after a specific number of items to directly stream the data. + * + * @see flush() + * + * @author Alexander Schranz + * + * Example usage: + * + * function loadArticles(): \Generator + * // some streamed loading + * yield ['title' => 'Article 1']; + * yield ['title' => 'Article 2']; + * yield ['title' => 'Article 3']; + * // recommended to use flush() after every specific number of items + * }), + * + * $response = new StreamedJsonResponse( + * // json structure with generators in which will be streamed + * [ + * '_embedded' => [ + * 'articles' => loadArticles(), // any generator which you want to stream as list of data + * ], + * ], + * ); + */ +class StreamedJsonResponse extends StreamedResponse +{ + private const PLACEHOLDER = '__symfony_json__'; + + /** + * @param mixed[] $data JSON Data containing PHP generators which will be streamed as list of data + * @param int $status The HTTP status code (200 "OK" by default) + * @param array $headers An array of HTTP headers + * @param int $encodingOptions Flags for the json_encode() function + */ + public function __construct( + private readonly array $data, + int $status = 200, + array $headers = [], + private int $encodingOptions = JsonResponse::DEFAULT_ENCODING_OPTIONS, + ) { + parent::__construct($this->stream(...), $status, $headers); + + if (!$this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + } + + private function stream(): void + { + $generators = []; + $structure = $this->data; + + array_walk_recursive($structure, function (&$item, $key) use (&$generators) { + if (self::PLACEHOLDER === $key) { + // if the placeholder is already in the structure it should be replaced with a new one that explode + // works like expected for the structure + $generators[] = $key; + } + + // generators should be used but for better DX all kind of Traversable and objects are supported + if (\is_object($item)) { + $generators[] = $item; + $item = self::PLACEHOLDER; + } elseif (self::PLACEHOLDER === $item) { + // if the placeholder is already in the structure it should be replaced with a new one that explode + // works like expected for the structure + $generators[] = $item; + } + }); + + $jsonEncodingOptions = \JSON_THROW_ON_ERROR | $this->encodingOptions; + $keyEncodingOptions = $jsonEncodingOptions & ~\JSON_NUMERIC_CHECK; + + $jsonParts = explode('"'.self::PLACEHOLDER.'"', json_encode($structure, $jsonEncodingOptions)); + + foreach ($generators as $index => $generator) { + // send first and between parts of the structure + echo $jsonParts[$index]; + + if ($generator instanceof \JsonSerializable || !$generator instanceof \Traversable) { + // the placeholders, JsonSerializable and none traversable items in the structure are rendered here + echo json_encode($generator, $jsonEncodingOptions); + + continue; + } + + $isFirstItem = true; + $startTag = '['; + + foreach ($generator as $key => $item) { + if ($isFirstItem) { + $isFirstItem = false; + // depending on the first elements key the generator is detected as a list or map + // we can not check for a whole list or map because that would hurt the performance + // of the streamed response which is the main goal of this response class + if (0 !== $key) { + $startTag = '{'; + } + + echo $startTag; + } else { + // if not first element of the generic, a separator is required between the elements + echo ','; + } + + if ('{' === $startTag) { + echo json_encode((string) $key, $keyEncodingOptions).':'; + } + + echo json_encode($item, $jsonEncodingOptions); + } + + echo '[' === $startTag ? ']' : '}'; + } + + // send last part of the structure + echo $jsonParts[array_key_last($jsonParts)]; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/StreamedJsonResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/StreamedJsonResponseTest.php new file mode 100644 index 0000000000000..e142672fd0658 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/StreamedJsonResponseTest.php @@ -0,0 +1,241 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\StreamedJsonResponse; + +class StreamedJsonResponseTest extends TestCase +{ + public function testResponseSimpleList() + { + $content = $this->createSendResponse( + [ + '_embedded' => [ + 'articles' => $this->generatorSimple('Article'), + 'news' => $this->generatorSimple('News'), + ], + ], + ); + + $this->assertSame('{"_embedded":{"articles":["Article 1","Article 2","Article 3"],"news":["News 1","News 2","News 3"]}}', $content); + } + + public function testResponseObjectsList() + { + $content = $this->createSendResponse( + [ + '_embedded' => [ + 'articles' => $this->generatorArray('Article'), + ], + ], + ); + + $this->assertSame('{"_embedded":{"articles":[{"title":"Article 1"},{"title":"Article 2"},{"title":"Article 3"}]}}', $content); + } + + public function testResponseWithoutGenerator() + { + // while it is not the intended usage, all kind of iterables should be supported for good DX + $content = $this->createSendResponse( + [ + '_embedded' => [ + 'articles' => ['Article 1', 'Article 2', 'Article 3'], + ], + ], + ); + + $this->assertSame('{"_embedded":{"articles":["Article 1","Article 2","Article 3"]}}', $content); + } + + public function testResponseWithPlaceholder() + { + // the placeholder must not conflict with generator injection + $content = $this->createSendResponse( + [ + '_embedded' => [ + 'articles' => $this->generatorArray('Article'), + 'placeholder' => '__symfony_json__', + 'news' => $this->generatorSimple('News'), + ], + 'placeholder' => '__symfony_json__', + ], + ); + + $this->assertSame('{"_embedded":{"articles":[{"title":"Article 1"},{"title":"Article 2"},{"title":"Article 3"}],"placeholder":"__symfony_json__","news":["News 1","News 2","News 3"]},"placeholder":"__symfony_json__"}', $content); + } + + public function testResponseWithMixedKeyType() + { + $content = $this->createSendResponse( + [ + '_embedded' => [ + 'list' => (function (): \Generator { + yield 0 => 'test'; + yield 'key' => 'value'; + })(), + 'map' => (function (): \Generator { + yield 'key' => 'value'; + yield 0 => 'test'; + })(), + 'integer' => (function (): \Generator { + yield 1 => 'one'; + yield 3 => 'three'; + })(), + ], + ] + ); + + $this->assertSame('{"_embedded":{"list":["test","value"],"map":{"key":"value","0":"test"},"integer":{"1":"one","3":"three"}}}', $content); + } + + public function testResponseOtherTraversable() + { + $arrayObject = new \ArrayObject(['__symfony_json__' => '__symfony_json__']); + + $iteratorAggregate = new class() implements \IteratorAggregate { + public function getIterator(): \Traversable + { + return new \ArrayIterator(['__symfony_json__']); + } + }; + + $jsonSerializable = new class() implements \IteratorAggregate, \JsonSerializable { + public function getIterator(): \Traversable + { + return new \ArrayIterator(['This should be ignored']); + } + + public function jsonSerialize(): mixed + { + return ['__symfony_json__' => '__symfony_json__']; + } + }; + + // while Generators should be used for performance reasons, the object should also work with any Traversable + // to make things easier for a developer + $content = $this->createSendResponse( + [ + 'arrayObject' => $arrayObject, + 'iteratorAggregate' => $iteratorAggregate, + 'jsonSerializable' => $jsonSerializable, + // add a Generator to make sure it still work in combination with other Traversable objects + 'articles' => $this->generatorArray('Article'), + ], + ); + + $this->assertSame('{"arrayObject":{"__symfony_json__":"__symfony_json__"},"iteratorAggregate":["__symfony_json__"],"jsonSerializable":{"__symfony_json__":"__symfony_json__"},"articles":[{"title":"Article 1"},{"title":"Article 2"},{"title":"Article 3"}]}', $content); + } + + public function testPlaceholderAsKeyAndValueInStructure() + { + $content = $this->createSendResponse( + [ + '__symfony_json__' => '__symfony_json__', + 'articles' => $this->generatorArray('Article'), + ], + ); + + $this->assertSame('{"__symfony_json__":"__symfony_json__","articles":[{"title":"Article 1"},{"title":"Article 2"},{"title":"Article 3"}]}', $content); + } + + public function testResponseStatusCode() + { + $response = new StreamedJsonResponse([], 201); + + $this->assertSame(201, $response->getStatusCode()); + } + + public function testPlaceholderAsObjectStructure() + { + $object = new class() { + public $__symfony_json__ = 'foo'; + public $bar = '__symfony_json__'; + }; + + $content = $this->createSendResponse( + [ + 'object' => $object, + // add a Generator to make sure it still work in combination with other object holding placeholders + 'articles' => $this->generatorArray('Article'), + ], + ); + + $this->assertSame('{"object":{"__symfony_json__":"foo","bar":"__symfony_json__"},"articles":[{"title":"Article 1"},{"title":"Article 2"},{"title":"Article 3"}]}', $content); + } + + public function testResponseHeaders() + { + $response = new StreamedJsonResponse([], 200, ['X-Test' => 'Test']); + + $this->assertSame('Test', $response->headers->get('X-Test')); + } + + public function testCustomContentType() + { + $response = new StreamedJsonResponse([], 200, ['Content-Type' => 'application/json+stream']); + + $this->assertSame('application/json+stream', $response->headers->get('Content-Type')); + } + + public function testEncodingOptions() + { + $response = new StreamedJsonResponse([ + '_embedded' => [ + 'count' => '2', // options are applied to the initial json encode + 'values' => (function (): \Generator { + yield 'with/unescaped/slash' => 'With/a/slash'; // options are applied to key and values + yield '3' => '3'; // numeric check for value, but not for the key + })(), + ], + ], encodingOptions: \JSON_UNESCAPED_SLASHES | \JSON_NUMERIC_CHECK); + + ob_start(); + $response->send(); + $content = ob_get_clean(); + + $this->assertSame('{"_embedded":{"count":2,"values":{"with/unescaped/slash":"With/a/slash","3":3}}}', $content); + } + + /** + * @param mixed[] $data + */ + private function createSendResponse(array $data): string + { + $response = new StreamedJsonResponse($data); + + ob_start(); + $response->send(); + + return ob_get_clean(); + } + + /** + * @return \Generator + */ + private function generatorSimple(string $test): \Generator + { + yield $test.' 1'; + yield $test.' 2'; + yield $test.' 3'; + } + + /** + * @return \Generator + */ + private function generatorArray(string $test): \Generator + { + yield ['title' => $test.' 1']; + yield ['title' => $test.' 2']; + yield ['title' => $test.' 3']; + } +} From 53bfee4440bdd65d52216ebdbec16132221050c1 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 15 Dec 2022 20:28:50 +0100 Subject: [PATCH 099/475] [ExpressionLanguage] Add `enum` expression function --- .../Component/ExpressionLanguage/CHANGELOG.md | 5 ++ .../ExpressionLanguage/ExpressionLanguage.php | 13 +++++ .../Tests/ExpressionLanguageTest.php | 49 +++++++++++++++++++ .../Tests/Fixtures/FooBackedEnum.php | 8 +++ .../Tests/Fixtures/FooEnum.php | 8 +++ 5 files changed, 83 insertions(+) create mode 100644 src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/FooBackedEnum.php create mode 100644 src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/FooEnum.php diff --git a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md index 9210fc4cc33fb..44a3478ff51b4 100644 --- a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md +++ b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add `enum` expression function + 6.2 --- diff --git a/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php b/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php index e076eb9bc56e0..ed233cd270c5a 100644 --- a/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php +++ b/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php @@ -138,6 +138,19 @@ public function registerProvider(ExpressionFunctionProviderInterface $provider) protected function registerFunctions() { $this->addFunction(ExpressionFunction::fromPhp('constant')); + + $this->addFunction(new ExpressionFunction('enum', + static fn ($str): string => sprintf("(\constant(\$v = (%s))) instanceof \UnitEnum ? \constant(\$v) : throw new \TypeError(\sprintf('The string \"%%s\" is not the name of a valid enum case.', \$v))", $str), + static function ($arguments, $str): \UnitEnum { + $value = \constant($str); + + if (!$value instanceof \UnitEnum) { + throw new \TypeError(sprintf('The string "%s" is not the name of a valid enum case.', $str)); + } + + return $value; + } + )); } private function getLexer(): Lexer diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php index 2efa7a3be4722..98a91600d6612 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php @@ -18,6 +18,8 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\ExpressionLanguage\ParsedExpression; use Symfony\Component\ExpressionLanguage\SyntaxError; +use Symfony\Component\ExpressionLanguage\Tests\Fixtures\FooBackedEnum; +use Symfony\Component\ExpressionLanguage\Tests\Fixtures\FooEnum; use Symfony\Component\ExpressionLanguage\Tests\Fixtures\TestProvider; class ExpressionLanguageTest extends TestCase @@ -78,6 +80,53 @@ public function testConstantFunction() $this->assertEquals('\constant("PHP_VERSION")', $expressionLanguage->compile('constant("PHP_VERSION")')); } + public function testEnumFunctionWithConstantThrows() + { + $this->expectException(\TypeError::class); + $this->expectExceptionMessage('The string "PHP_VERSION" is not the name of a valid enum case.'); + $expressionLanguage = new ExpressionLanguage(); + $expressionLanguage->evaluate('enum("PHP_VERSION")'); + } + + public function testCompiledEnumFunctionWithConstantThrows() + { + $this->expectException(\TypeError::class); + $this->expectExceptionMessage('The string "PHP_VERSION" is not the name of a valid enum case.'); + $expressionLanguage = new ExpressionLanguage(); + eval($expressionLanguage->compile('enum("PHP_VERSION")').';'); + } + + public function testEnumFunction() + { + $expressionLanguage = new ExpressionLanguage(); + $this->assertSame(FooEnum::Foo, $expressionLanguage->evaluate('enum("Symfony\\\\Component\\\\ExpressionLanguage\\\\Tests\\\\Fixtures\\\\FooEnum::Foo")')); + } + + public function testCompiledEnumFunction() + { + $result = null; + $expressionLanguage = new ExpressionLanguage(); + eval(sprintf('$result = %s;', $expressionLanguage->compile('enum("Symfony\\\\Component\\\\ExpressionLanguage\\\\Tests\\\\Fixtures\\\\FooEnum::Foo")'))); + + $this->assertSame(FooEnum::Foo, $result); + } + + public function testBackedEnumFunction() + { + $expressionLanguage = new ExpressionLanguage(); + $this->assertSame(FooBackedEnum::Bar, $expressionLanguage->evaluate('enum("Symfony\\\\Component\\\\ExpressionLanguage\\\\Tests\\\\Fixtures\\\\FooBackedEnum::Bar")')); + $this->assertSame('Foo', $expressionLanguage->evaluate('enum("Symfony\\\\Component\\\\ExpressionLanguage\\\\Tests\\\\Fixtures\\\\FooBackedEnum::Bar").value')); + } + + public function testCompiledEnumFunctionWithBackedEnum() + { + $result = null; + $expressionLanguage = new ExpressionLanguage(); + eval(sprintf('$result = %s;', $expressionLanguage->compile('enum("Symfony\\\\Component\\\\ExpressionLanguage\\\\Tests\\\\Fixtures\\\\FooBackedEnum::Bar")'))); + + $this->assertSame(FooBackedEnum::Bar, $result); + } + public function testProviders() { $expressionLanguage = new ExpressionLanguage(null, [new TestProvider()]); diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/FooBackedEnum.php b/src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/FooBackedEnum.php new file mode 100644 index 0000000000000..8bf81231b4dac --- /dev/null +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/FooBackedEnum.php @@ -0,0 +1,8 @@ + Date: Wed, 30 Nov 2022 16:47:42 +0100 Subject: [PATCH 100/475] add reject to MessageEvent to stop sending mail --- src/Symfony/Component/Mailer/CHANGELOG.md | 5 +++++ src/Symfony/Component/Mailer/Event/MessageEvent.php | 13 ++++++++++++- .../Mailer/Transport/AbstractTransport.php | 4 ++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Mailer/CHANGELOG.md b/src/Symfony/Component/Mailer/CHANGELOG.md index 772f50717334f..90fdf5d47811f 100644 --- a/src/Symfony/Component/Mailer/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add `reject()` in `MessageEvent` to reject sending mail + 6.2 --- diff --git a/src/Symfony/Component/Mailer/Event/MessageEvent.php b/src/Symfony/Component/Mailer/Event/MessageEvent.php index 083b8f8435b56..906d4459fabd7 100644 --- a/src/Symfony/Component/Mailer/Event/MessageEvent.php +++ b/src/Symfony/Component/Mailer/Event/MessageEvent.php @@ -28,6 +28,7 @@ final class MessageEvent extends Event private Envelope $envelope; private string $transport; private bool $queued; + private bool $rejected = false; /** @var StampInterface[] */ private array $stamps = []; @@ -70,10 +71,20 @@ public function isQueued(): bool return $this->queued; } + public function isRejected(): bool + { + return $this->rejected; + } + + public function reject(): void + { + $this->rejected = true; + } + public function addStamp(StampInterface $stamp): void { if (!$this->queued) { - throw new LogicException(sprintf('Cannot call "%s()" on a message that is not meant to be queued', __METHOD__)); + throw new LogicException(sprintf('Cannot call "%s()" on a message that is not meant to be queued.', __METHOD__)); } $this->stamps[] = $stamp; diff --git a/src/Symfony/Component/Mailer/Transport/AbstractTransport.php b/src/Symfony/Component/Mailer/Transport/AbstractTransport.php index a6710d007da02..f3ee80f890c8d 100644 --- a/src/Symfony/Component/Mailer/Transport/AbstractTransport.php +++ b/src/Symfony/Component/Mailer/Transport/AbstractTransport.php @@ -82,6 +82,10 @@ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessa $sentMessage = new SentMessage($message, $envelope); + if ($event->isRejected()) { + return $sentMessage; + } + try { $this->doSend($sentMessage); } catch (\Throwable $error) { From ee020be06bd5c0c0aca60ff873c316d68f5109f8 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 31 Dec 2022 11:24:53 +0100 Subject: [PATCH 101/475] [Mailer] Add the possibility to reject a message --- src/Symfony/Component/Mailer/CHANGELOG.md | 2 +- src/Symfony/Component/Mailer/Mailer.php | 4 +++ .../Component/Mailer/Tests/MailerTest.php | 34 +++++++++++++++++++ .../Tests/Transport/AbstractTransportTest.php | 31 +++++++++++++++++ .../Mailer/Transport/AbstractTransport.php | 8 ++--- 5 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Mailer/CHANGELOG.md b/src/Symfony/Component/Mailer/CHANGELOG.md index 90fdf5d47811f..877872ca8ed7e 100644 --- a/src/Symfony/Component/Mailer/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 6.3 --- - * Add `reject()` in `MessageEvent` to reject sending mail + * Add `MessageEvent::reject()` to allow rejecting an email before sending it 6.2 --- diff --git a/src/Symfony/Component/Mailer/Mailer.php b/src/Symfony/Component/Mailer/Mailer.php index c9f8b15aa85c1..cd305a65c4926 100644 --- a/src/Symfony/Component/Mailer/Mailer.php +++ b/src/Symfony/Component/Mailer/Mailer.php @@ -56,6 +56,10 @@ public function send(RawMessage $message, Envelope $envelope = null): void $event = new MessageEvent($clonedMessage, $clonedEnvelope, (string) $this->transport, true); $this->dispatcher->dispatch($event); $stamps = $event->getStamps(); + + if ($event->isRejected()) { + return; + } } try { diff --git a/src/Symfony/Component/Mailer/Tests/MailerTest.php b/src/Symfony/Component/Mailer/Tests/MailerTest.php index a369216e23cbc..3167a8270ea0b 100644 --- a/src/Symfony/Component/Mailer/Tests/MailerTest.php +++ b/src/Symfony/Component/Mailer/Tests/MailerTest.php @@ -13,14 +13,19 @@ use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Mailer\Envelope as MailerEnvelope; use Symfony\Component\Mailer\Event\MessageEvent; use Symfony\Component\Mailer\Exception\LogicException; use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\Transport\AbstractTransport; use Symfony\Component\Mailer\Transport\NullTransport; use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Stamp\StampInterface; +use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; use Symfony\Component\Mime\RawMessage; @@ -78,4 +83,33 @@ public function dispatch($message, array $stamps = []): Envelope self::assertCount(1, $bus->stamps); self::assertSame([$stamp], $bus->stamps); } + + public function testRejectMessage() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(MessageEvent::class, fn (MessageEvent $event) => $event->reject()); + + $transport = new class($dispatcher, $this) extends AbstractTransport { + public function __construct(EventDispatcherInterface $dispatcher, private TestCase $test) + { + parent::__construct($dispatcher); + } + + protected function doSend(SentMessage $message): void + { + $this->test->fail('This should never be called as message is rejected.'); + } + + public function __toString(): string + { + return 'fake://'; + } + }; + $mailer = new Mailer($transport); + + $message = new RawMessage(''); + $envelope = new MailerEnvelope(new Address('fabien@example.com'), [new Address('helene@example.com')]); + $mailer->send($message, $envelope); + $this->assertTrue(true); + } } diff --git a/src/Symfony/Component/Mailer/Tests/Transport/AbstractTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/AbstractTransportTest.php index 42c23fcd735e2..7f64429002c21 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/AbstractTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/AbstractTransportTest.php @@ -15,9 +15,13 @@ use Symfony\Bridge\Twig\Mime\BodyRenderer; use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Event\MessageEvent; use Symfony\Component\Mailer\EventListener\MessageListener; use Symfony\Component\Mailer\Exception\LogicException; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\Transport\AbstractTransport; use Symfony\Component\Mailer\Transport\NullTransport; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\RawMessage; @@ -78,4 +82,31 @@ public function testRenderedTemplatedEmail() $sentMessage = $transport->send((new TemplatedEmail())->to('me@example.com')->from('me@example.com')->htmlTemplate('tpl')); $this->assertMatchesRegularExpression('/Some message/', $sentMessage->getMessage()->toString()); } + + public function testRejectMessage() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(MessageEvent::class, fn (MessageEvent $event) => $event->reject()); + + $transport = new cl 10000 ass($dispatcher, $this) extends AbstractTransport { + public function __construct(EventDispatcherInterface $dispatcher, private TestCase $test) + { + parent::__construct($dispatcher); + } + + protected function doSend(SentMessage $message): void + { + $this->test->fail('This should never be called as message is rejected.'); + } + + public function __toString(): string + { + return 'fake://'; + } + }; + + $message = new RawMessage(''); + $envelope = new Envelope(new Address('fabien@example.com'), [new Address('helene@example.com')]); + $this->assertNull($transport->send($message, $envelope)); + } } diff --git a/src/Symfony/Component/Mailer/Transport/AbstractTransport.php b/src/Symfony/Component/Mailer/Transport/AbstractTransport.php index f3ee80f890c8d..6598dccfcc09e 100644 --- a/src/Symfony/Component/Mailer/Transport/AbstractTransport.php +++ b/src/Symfony/Component/Mailer/Transport/AbstractTransport.php @@ -73,6 +73,10 @@ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessa $event = new MessageEvent($message, $envelope, (string) $this); $this->dispatcher->dispatch($event); + if ($event->isRejected()) { + return null; + } + $envelope = $event->getEnvelope(); $message = $event->getMessage(); @@ -82,10 +86,6 @@ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessa $sentMessage = new SentMessage($message, $envelope); - if ($event->isRejected()) { - return $sentMessage; - } - try { $this->doSend($sentMessage); } catch (\Throwable $error) { From 5733cf767760999139dcf82a70e316a5f31687c1 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 31 Dec 2022 12:54:12 +0100 Subject: [PATCH 102/475] [Mailer] Stop propagation when an email is rejected --- src/Symfony/Component/Mailer/Event/MessageEvent.php | 1 + src/Symfony/Component/Mailer/Tests/MailerTest.php | 3 ++- .../Component/Mailer/Tests/Transport/AbstractTransportTest.php | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Mailer/Event/MessageEvent.php b/src/Symfony/Component/Mailer/Event/MessageEvent.php index 906d4459fabd7..f00fdd52ca24c 100644 --- a/src/Symfony/Component/Mailer/Event/MessageEvent.php +++ b/src/Symfony/Component/Mailer/Event/MessageEvent.php @@ -79,6 +79,7 @@ public function isRejected(): bool public function reject(): void { $this->rejected = true; + $this->stopPropagation(); } public function addStamp(StampInterface $stamp): void diff --git a/src/Symfony/Component/Mailer/Tests/MailerTest.php b/src/Symfony/Component/Mailer/Tests/MailerTest.php index 3167a8270ea0b..95ab9d9391c7b 100644 --- a/src/Symfony/Component/Mailer/Tests/MailerTest.php +++ b/src/Symfony/Component/Mailer/Tests/MailerTest.php @@ -87,7 +87,8 @@ public function dispatch($message, array $stamps = []): Envelope public function testRejectMessage() { $dispatcher = new EventDispatcher(); - $dispatcher->addListener(MessageEvent::class, fn (MessageEvent $event) => $event->reject()); + $dispatcher->addListener(MessageEvent::class, fn (MessageEvent $event) => $event->reject(), 255); + $dispatcher->addListener(MessageEvent::class, fn () => throw new \RuntimeException('Should never be called.')); $transport = new class($dispatcher, $this) extends AbstractTransport { public function __construct(EventDispatcherInterface $dispatcher, private TestCase $test) diff --git a/src/Symfony/Component/Mailer/Tests/Transport/AbstractTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/AbstractTransportTest.php index 7f64429002c21..19d574f736079 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/AbstractTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/AbstractTransportTest.php @@ -86,7 +86,8 @@ public function testRenderedTemplatedEmail() public function testRejectMessage() { $dispatcher = new EventDispatcher(); - $dispatcher->addListener(MessageEvent::class, fn (MessageEvent $event) => $event->reject()); + $dispatcher->addListener(MessageEvent::class, fn (MessageEvent $event) => $event->reject(), 255); + $dispatcher->addListener(MessageEvent::class, fn () => throw new \RuntimeException('Should never be called.')); $transport = new class($dispatcher, $this) extends AbstractTransport { public function __construct(EventDispatcherInterface $dispatcher, private TestCase $test) From d0efd41d0268ddb4a55ea85002165f3c6a5f80ae Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 1 Jan 2023 09:39:23 +0100 Subject: [PATCH 103/475] Bump LICENSE year --- src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/Termii/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE b/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE index 0ece8964f767d..074eb2b39259e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Fabien Potencier +Copyright (c) 2022-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE b/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE index 0ece8964f767d..074eb2b39259e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Fabien Potencier +Copyright (c) 2022-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE b/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE index 0ece8964f767d..074eb2b39259e 100644 --- a/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Fabien Potencier +Copyright (c) 2022-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE b/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE index 0ece8964f767d..074eb2b39259e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Fabien Potencier +Copyright (c) 2022-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE b/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE index 0ece8964f767d..074eb2b39259e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Fabien Potencier +Copyright (c) 2022-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE b/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE index 0ece8964f767d..074eb2b39259e 100644 --- a/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Fabien Potencier +Copyright (c) 2022-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE b/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE index 0ece8964f767d..074eb2b39259e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Fabien Potencier +Copyright (c) 2022-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE b/src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE index 0ece8964f767d..074eb2b39259e 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Fabien Potencier +Copyright (c) 2022-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 8d3e897bf7c1ad10371a65e99445688b976c211b Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 6 Dec 2022 17:06:28 +0100 Subject: [PATCH 104/475] [PhpUnitBridge] Add `enum_exists` mock --- src/Symfony/Bridge/PhpUnit/CHANGELOG.md | 5 ++ .../Bridge/PhpUnit/ClassExistsMock.php | 24 +++++- .../PhpUnit/Tests/ClassExistsMockTest.php | 46 ++++++++++++ .../PhpUnit/Tests/EnumExistsMockTest.php | 74 +++++++++++++++++++ .../PhpUnit/Tests/Fixtures/ExistingEnum.php | 16 ++++ .../Tests/Fixtures/ExistingEnumReal.php | 16 ++++ src/Symfony/Bridge/PhpUnit/composer.json | 3 +- 7 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnum.php create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnumReal.php diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index e7e3e29862bd4..9c664176565b7 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add support for mocking the `enum_exists` function + 6.2 --- diff --git a/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php b/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php index 70fdb9f9631ad..d79624ec5fdde 100644 --- a/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php +++ b/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php @@ -18,16 +18,29 @@ class ClassExistsMock { private static $classes = []; + private static $enums = []; + /** * Configures the classes to be checked upon existence. * - * @param array $classes Mocked class names as keys (case sensitive, without leading root namespace slash) and booleans as values + * @param array $classes Mocked class names as keys (case-sensitive, without leading root namespace slash) and booleans as values */ public static function withMockedClasses(array $classes) { self::$classes = $classes; } + /** + * Configures the enums to be checked upon existence. + * + * @param array $enums Mocked enums names as keys (case-sensitive, without leading root namespace slash) and booleans as values + */ + public static function withMockedEnums(array $enums) + { + self::$enums = $enums; + self::$classes += $enums; + } + public static function class_exists($name, $autoload = true) { $name = ltrim($name, '\\'); @@ -49,6 +62,13 @@ public static function trait_exists($name, $autoload = true) return isset(self::$classes[$name]) ? (bool) self::$classes[$name] : \trait_exists($name, $autoload); } + public static function enum_exists($name, $autoload = true) + { + $name = ltrim($name, '\\'); + + return isset(self::$enums[$name]) ? (bool) self::$enums[$name] : \enum_exists($name, $autoload); + } + public static function register($class) { $self = static::class; @@ -61,7 +81,7 @@ public static function register($class) $mockedNs[] = substr($class, 6, strrpos($class, '\\') - 6); } foreach ($mockedNs as $ns) { - foreach (['class', 'interface', 'trait'] as $type) { + foreach (['class', 'interface', 'trait', 'enum'] as $type) { if (\function_exists($ns.'\\'.$type.'_exists')) { continue; } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/ClassExistsMockTest.php b/src/Symfony/Bridge/PhpUnit/Tests/ClassExistsMockTest.php index 3e3d5771b1b10..58c01646a8a61 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/ClassExistsMockTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/ClassExistsMockTest.php @@ -31,6 +31,10 @@ protected function setUp(): void ExistingTrait::class => false, 'NonExistingTrait' => true, ]); + + ClassExistsMock::withMockedEnums([ + 'NonExistingEnum' => true, + ]); } public function testClassExists() @@ -53,6 +57,26 @@ public function testClassExists() $this->assertFalse(class_exists('\\NonExistingClassReal', false)); } + public function testEnumExistsOnClasses() + { + $this->assertFalse(enum_exists(ExistingClass::class)); + $this->assertFalse(enum_exists(ExistingClass::class, false)); + $this->assertFalse(enum_exists('\\'.ExistingClass::class)); + $this->assertFalse(enum_exists('\\'.ExistingClass::class, false)); + $this->assertFalse(enum_exists('NonExistingClass')); + $this->assertFalse(enum_exists('NonExistingClass', false)); + $this->assertFalse(enum_exists('\\NonExistingClass')); + $this->assertFalse(enum_exists('\\NonExistingClass', false)); + $this->assertFalse(enum_exists(ExistingClassReal::class)); + $this->assertFalse(enum_exists(ExistingClassReal::class, false)); + $this->assertFalse(enum_exists('\\'.ExistingClassReal::class)); + $this->assertFalse(enum_exists('\\'.ExistingClassReal::class, false)); + $this->assertFalse(enum_exists('NonExistingClassReal')); + $this->assertFalse(enum_exists('NonExistingClassReal', false)); + $this->assertFalse(enum_exists('\\NonExistingClassReal')); + $this->assertFalse(enum_exists('\\NonExistingClassReal', false)); + } + public function testInterfaceExists() { $this->assertFalse(interface_exists(ExistingInterface::class)); @@ -92,6 +116,28 @@ public function testTraitExists() $this->assertFalse(trait_exists('\\NonExistingTraitReal')); $this->assertFalse(trait_exists('\\NonExistingTraitReal', false)); } + + public function testEnumExists() + { + $this->assertTrue(enum_exists('NonExistingEnum')); + $this->assertTrue(enum_exists('NonExistingEnum', false)); + $this->assertTrue(enum_exists('\\NonExistingEnum')); + $this->assertTrue(enum_exists('\\NonExistingEnum', false)); + $this->assertFalse(enum_exists('NonExistingClassReal')); + $this->assertFalse(enum_exists('NonExistingClassReal', false)); + $this->assertFalse(enum_exists('\\NonExistingEnumReal')); + $this->assertFalse(enum_exists('\\NonExistingEnumReal', false)); + } + + public function testClassExistsOnEnums() + { + $this->assertTrue(class_exists('NonExistingEnum')); + $this->assertTrue(class_exists('NonExistingEnum', false)); + $this->assertTrue(class_exists('\\NonExistingEnum')); + $this->assertTrue(class_exists('\\NonExistingEnum', false)); + $this->assertFalse(class_exists('\\NonExistingEnumReal')); + $this->assertFalse(class_exists('\\NonExistingEnumReal', false)); + } } class ExistingClass diff --git a/src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php b/src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php new file mode 100644 index 0000000000000..8115dc1316538 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ClassExistsMock; +use Symfony\Bridge\PhpUnit\Tests\Fixtures\ExistingEnum; +use Symfony\Bridge\PhpUnit\Tests\Fixtures\ExistingEnumReal; + +/** + * @requires PHP 8.1 + */ +class EnumExistsMockTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + ClassExistsMock::register(__CLASS__); + } + + protected function setUp(): void + { + ClassExistsMock::withMockedEnums([ + ExistingEnum::class => false, + 'NonExistingEnum' => true, + ]); + } + + public function testClassExists() + { + $this->assertFalse(class_exists(ExistingEnum::class)); + $this->assertFalse(class_exists(ExistingEnum::class, false)); + $this->assertFalse(class_exists('\\'.ExistingEnum::class)); + $this->assertFalse(class_exists('\\'.ExistingEnum::class, false)); + $this->assertTrue(class_exists('NonExistingEnum')); + $this->assertTrue(class_exists('NonExistingEnum', false)); + $this->assertTrue(class_exists('\\NonExistingEnum')); + $this->assertTrue(class_exists('\\NonExistingEnum', false)); + $this->assertTrue(class_exists(ExistingEnumReal::class)); + $this->assertTrue(class_exists(ExistingEnumReal::class, false)); + $this->assertTrue(class_exists('\\'.ExistingEnumReal::class)); + $this->assertTrue(class_exists('\\'.ExistingEnumReal::class, false)); + $this->assertFalse(class_exists('\\NonExistingEnumReal')); + $this->assertFalse(class_exists('\\NonExistingEnumReal', false)); + } + + public function testEnumExists() + { + $this->assertFalse(enum_exists(ExistingEnum::class)); + $this->assertFalse(enum_exists(ExistingEnum::class, false)); + $this->assertFalse(enum_exists('\\'.ExistingEnum::class)); + $this->assertFalse(enum_exists('\\'.ExistingEnum::class, false)); + $this->assertTrue(enum_exists('NonExistingEnum')); + $this->assertTrue(enum_exists('NonExistingEnum', false)); + $this->assertTrue(enum_exists('\\NonExistingEnum')); + $this->assertTrue(enum_exists('\\NonExistingEnum', false)); + $this->assertTrue(enum_exists(ExistingEnumReal::class)); + $this->assertTrue(enum_exists(ExistingEnumReal::class, false)); + $this->assertTrue(enum_exists('\\'.ExistingEnumReal::class)); + $this->assertTrue(enum_exists('\\'.ExistingEnumReal::class, false)); + $this->assertFalse(enum_exists('NonExistingClassReal')); + $this->assertFalse(enum_exists('NonExistingClassReal', false)); + $this->assertFalse(enum_exists('\\NonExistingEnumReal')); + $this->assertFalse(enum_exists('\\NonExistingEnumReal', false)); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnum.php b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnum.php new file mode 100644 index 0000000000000..039e293b0c0d7 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnum.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests\Fixtures; + +enum ExistingEnum +{ +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnumReal.php b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnumReal.php new file mode 100644 index 0000000000000..028090638d060 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnumReal.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests\Fixtures; + +enum ExistingEnumReal +{ +} diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index 85b11227d01c0..75fce743284aa 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -22,7 +22,8 @@ }, "require-dev": { "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/error-handler": "^5.4|^6.0" + "symfony/error-handler": "^5.4|^6.0", + "symfony/polyfill-php81": "^1.27" }, "suggest": { "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" From e4ba7b7552f305243380af2ccfe82a712a1671c4 Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Thu, 29 Dec 2022 16:19:30 +0100 Subject: [PATCH 105/475] [HttpFoundation] ParameterBag::getEnum() --- .../Component/HttpFoundation/CHANGELOG.md | 1 + .../Component/HttpFoundation/InputBag.php | 19 ++++++++++ .../Component/HttpFoundation/ParameterBag.php | 25 +++++++++++++ .../HttpFoundation/Tests/InputBagTest.php | 17 +++++++++ .../HttpFoundation/Tests/ParameterBagTest.php | 35 +++++++++++++++++++ 5 files changed, 97 insertions(+) diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 1aaf6ed0f8507..208f466d749b8 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.3 --- + * Add `ParameterBag::getEnum()` * Create migration for session table when pdo handler is used 6.2 diff --git a/src/Symfony/Component/HttpFoundation/InputBag.php b/src/Symfony/Component/HttpFoundation/InputBag.php index 877ac60f3aefd..446b82132b140 100644 --- a/src/Symfony/Component/HttpFoundation/InputBag.php +++ b/src/Symfony/Component/HttpFoundation/InputBag.php @@ -73,6 +73,25 @@ public function set(string $key, mixed $value) $this->parameters[$key] = $value; } + /** + * Returns the parameter value converted to an enum. + * + * @template T of \BackedEnum + * + * @param class-string $class + * @param ?T $default + * + * @return ?T + */ + public function getEnum(string $key, string $class, \BackedEnum $default = null): ?\BackedEnum + { + try { + return parent::getEnum($key, $class, $default); + } catch (\UnexpectedValueException $e) { + throw new BadRequestException($e->getMessage(), $e->getCode(), $e); + } + } + public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed { $value = $this->has($key) ? $this->all()[$key] : $default; diff --git a/src/Symfony/Component/HttpFoundation/ParameterBag.php b/src/Symfony/Component/HttpFoundation/ParameterBag.php index 72c8f0949c5d4..9df9604e6c0ef 100644 --- a/src/Symfony/Component/HttpFoundation/ParameterBag.php +++ b/src/Symfony/Component/HttpFoundation/ParameterBag.php @@ -141,6 +141,31 @@ public function getBoolean(string $key, bool $default = false): bool return $this->filter($key, $default, \FILTER_VALIDATE_BOOL); } + /** + * Returns the parameter value converted to an enum. + * + * @template T of \BackedEnum + * + * @param class-string $class + * @param ?T $default + * + * @return ?T + */ + public function getEnum(string $key, string $class, \BackedEnum $default = null): ?\BackedEnum + { + $value = $this->get($key); + + if (null === $value) { + return $default; + } + + try { + return $class::from($value); + } catch (\ValueError|\TypeError $e) { + throw new \UnexpectedValueException(sprintf('Parameter "%s" cannot be converted to enum: %s.', $key, $e->getMessage()), $e->getCode(), $e); + } + } + /** * Filter key. * diff --git a/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php index 696318e91ea98..ccb4779ef35dc 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php @@ -106,4 +106,21 @@ public function testFilterArrayWithoutArrayFlag() $bag = new InputBag(['foo' => ['bar', 'baz']]); $bag->filter('foo', \FILTER_VALIDATE_INT); } + + public function testGetEnum() + { + $bag = new InputBag(['valid-value' => 1]); + + $this->assertSame(Foo::Bar, $bag->getEnum('valid-value', Foo::class)); + } + + public function testGetEnumThrowsExceptionWithInvalidValue() + { + $bag = new InputBag(['invalid-value' => 2]); + + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum "Symfony\Component\HttpFoundation\Tests\Foo".'); + + $this->assertNull($bag->getEnum('invalid-value', Foo::class)); + } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php index 1b60fb2418008..43aaade7efa16 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php @@ -226,4 +226,39 @@ public function testGetBoolean() $this->assertFalse($bag->getBoolean('string_false'), '->getBoolean() gets the string false as boolean false'); $this->assertFalse($bag->getBoolean('unknown'), '->getBoolean() returns false if a parameter is not defined'); } + + public function testGetEnum() + { + $bag = new ParameterBag(['valid-value' => 1]); + + $this->assertSame(Foo::Bar, $bag->getEnum('valid-value', Foo::class)); + + $this->assertNull($bag->getEnum('invalid-key', Foo::class)); + $this->assertSame(Foo::Bar, $bag->getEnum('invalid-key', Foo::class, Foo::Bar)); + } + + public function testGetEnumThrowsExceptionWithNotBackingValue() + { + $bag = new ParameterBag(['invalid-value' => 2]); + + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum "Symfony\Component\HttpFoundation\Tests\Foo".'); + + $this->assertNull($bag->getEnum('invalid-value', Foo::class)); + } + + public function testGetEnumThrowsExceptionWithInvalidValueType() + { + $bag = new ParameterBag(['invalid-value' => ['foo']]); + + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: Symfony\Component\HttpFoundation\Tests\Foo::from(): Argument #1 ($value) must be of type int, array given.'); + + $this->assertNull($bag->getEnum('invalid-value', Foo::class)); + } +} + +enum Foo: int +{ + case Bar = 1; } From 2df462301c35008bdea5d50b2b26dd533e6074c9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 4 Jan 2023 17:46:36 +0100 Subject: [PATCH 106/475] [Clock] Fix unitialized variable --- src/Symfony/Component/Clock/Clock.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Clock/Clock.php b/src/Symfony/Component/Clock/Clock.php index 8d72b8de99f1b..5148cde9f2ad5 100644 --- a/src/Symfony/Component/Clock/Clock.php +++ b/src/Symfony/Component/Clock/Clock.php @@ -46,14 +46,14 @@ public static function set(PsrClockInterface $clock): void public function now(): \DateTimeImmutable { - $now = ($this->clock ?? self::$globalClock)->now(); + $now = ($this->clock ?? self::get())->now(); return isset($this->timezone) ? $now->setTimezone($this->timezone) : $now; } public function sleep(float|int $seconds): void { - $clock = $this->clock ?? self::$globalClock; + $clock = $this->clock ?? self::get(); if ($clock instanceof ClockInterface) { $clock->sleep($seconds); From 8e8772d2c11fa47d5cde8d7c982c5ad9cb9f782d Mon Sep 17 00:00:00 2001 From: Alexis Lefebvre Date: Thu, 5 Jan 2023 03:10:09 +0100 Subject: [PATCH 107/475] remove double required annotation + attribute --- .../Bundle/FrameworkBundle/Controller/AbstractController.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index cef10efcd1a29..647ef98aee258 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -58,9 +58,6 @@ abstract class AbstractController implements ServiceSubscriberInterface */ protected $container; - /** - * @required - */ #[Required] public function setContainer(ContainerInterface $container): ?ContainerInterface { From 0a2563d561ffa016f10cb3dbcf574a4f39bd788a Mon Sep 17 00:00:00 2001 From: Dejan Angelov Date: Wed, 21 Dec 2022 21:00:06 +0100 Subject: [PATCH 108/475] [HttpKernel] Allow using `#[WithLogLevel]` for setting custom log level for exceptions --- .../HttpKernel/Attribute/WithLogLevel.php | 31 ++++++++++++ src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../EventListener/ErrorListener.php | 50 +++++++++++++------ .../Tests/Attribute/WithLogLevelTest.php | 39 +++++++++++++++ .../Tests/EventListener/ErrorListenerTest.php | 41 +++++++++++++++ 5 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 src/Symfony/Component/HttpKernel/Attribute/WithLogLevel.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/Attribute/WithLogLevelTest.php diff --git a/src/Symfony/Component/HttpKernel/Attribute/WithLogLevel.php b/src/Symfony/Component/HttpKernel/Attribute/WithLogLevel.php new file mode 100644 index 0000000000000..762b077043ae2 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Attribute/WithLogLevel.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Psr\Log\LogLevel; + +/** + * @author Dejan Angelov + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +final class WithLogLevel +{ + /** + * @param LogLevel::* $level + */ + public function __construct(public readonly string $level) + { + if (!\defined('Psr\Log\LogLevel::'.strtoupper($this->level))) { + throw new \InvalidArgumentException(sprintf('Invalid log level "%s".', $this->level)); + } + } +} diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index c823fbd0cb5c4..b24b2e4fa37d8 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * `FileProfilerStorage` removes profiles automatically after two days * Add `#[HttpStatus]` for defining status codes for exceptions * Use an instance of `Psr\Clock\ClockInterface` to generate the current date time in `DateTimeValueResolver` + * Add `#[WithLogLevel]` for defining log levels for exceptions 6.2 --- diff --git a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php index 11a060903addb..d131fd2fb9c0a 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php @@ -12,10 +12,12 @@ namespace Symfony\Component\HttpKernel\EventListener; use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\HttpStatus; +use Symfony\Component\HttpKernel\Attribute\WithLogLevel; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; @@ -52,14 +54,7 @@ public function __construct(string|object|array|null $controller, LoggerInterfac public function logKernelException(ExceptionEvent $event) { $throwable = $event->getThrowable(); - $logLevel = null; - - foreach ($this->exceptionsMapping as $class => $config) { - if ($throwable instanceof $class && $config['log_level']) { - $logLevel = $config['log_level']; - break; - } - } + $logLevel = $this->resolveLogLevel($throwable); foreach ($this->exceptionsMapping as $class => $config) { if (!$throwable instanceof $class || !$config['status_code']) { @@ -170,15 +165,40 @@ public static function getSubscribedEvents(): array */ protected function logException(\Throwable $exception, string $message, string $logLevel = null): void { - if (null !== $this->logger) { - if (null !== $logLevel) { - $this->logger->log($logLevel, $message, ['exception' => $exception]); - } elseif (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) { - $this->logger->critical($message, ['exception' => $exception]); - } else { - $this->logger->error($message, ['exception' => $exception]); + if (null === $this->logger) { + return; + } + + $logLevel ??= $this->resolveLogLevel($exception); + + $this->logger->log($logLevel, $message, ['exception' => $exception]); + } + + /** + * Resolves the level to be used when logging the exception. + */ + private function resolveLogLevel(\Throwable $throwable): string + { + foreach ($this->exceptionsMapping as $class => $config) { + if ($throwable instanceof $class && $config['log_level']) { + return $config['log_level']; } } + + $attributes = (new \ReflectionClass($throwable))->getAttributes(WithLogLevel::class); + + if ($attributes) { + /** @var WithLogLevel $instance */ + $instance = $attributes[0]->newInstance(); + + return $instance->level; + } + + if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() >= 500) { + return LogLevel::CRITICAL; + } + + return LogLevel::ERROR; } /** diff --git a/src/Symfony/Component/HttpKernel/Tests/Attribute/WithLogLevelTest.php b/src/Symfony/Component/HttpKernel/Tests/Attribute/WithLogLevelTest.php new file mode 100644 index 0000000000000..0b8905f9ea4f6 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/Attribute/WithLogLevelTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Attribute; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LogLevel; +use Symfony\Component\HttpKernel\Attribute\WithLogLevel; + +/** + * @author Dejan Angelov + */ +class WithLogLevelTest extends TestCase +{ + public function testWithValidLogLevel() + { + $logLevel = LogLevel::NOTICE; + + $attribute = new WithLogLevel($logLevel); + + $this->assertSame($logLevel, $attribute->level); + } + + public function testWithInvalidLogLevel() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid log level "invalid".'); + + new WithLogLevel('invalid'); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php index 5090eb11cbb79..aa878849ffa8e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php @@ -13,11 +13,13 @@ use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\HttpStatus; +use Symfony\Component\HttpKernel\Attribute\WithLogLevel; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ExceptionEvent; @@ -118,6 +120,40 @@ public function testHandleWithLoggerAndCustomConfiguration() $this->assertCount(1, $logger->getLogs('warning')); } + public function testHandleWithLogLevelAttribute() + { + $request = new Request(); + $event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new WarningWithLogLevelAttribute()); + $logger = new TestLogger(); + $l = new ErrorListener('not used', $logger); + + $l->logKernelException($event); + $l->onKernelException($event); + + $this->assertEquals(0, $logger->countErrors()); + $this->assertCount(0, $logger->getLogs('critical')); + $this->assertCount(1, $logger->getLogs('warning')); + } + + public function testHandleWithLogLevelAttributeAndCustomConfiguration() + { + $request = new Request(); + $event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new WarningWithLogLevelAttribute()); + $logger = new TestLogger(); + $l = new ErrorListener('not used', $logger, false, [ + WarningWithLogLevelAttribute::class => [ + 'log_level' => 'info', + 'status_code' => 401, + ], + ]); + $l->logKernelException($event); + $l->onKernelException($event); + + $this->assertEquals(0, $logger->countErrors()); + $this->assertCount(0, $logger->getLogs('warning')); + $this->assertCount(1, $logger->getLogs('info')); + } + /** * @dataProvider exceptionWithAttributeProvider */ @@ -312,3 +348,8 @@ class WithCustomUserProvidedAttribute extends \Exception class WithGeneralAttribute extends \Exception { } + +#[WithLogLevel(LogLevel::WARNING)] +class WarningWithLogLevelAttribute extends \Exception +{ +} From fece76621b2f3ba0ea084372eead3c39689526bf Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 5 Jan 2023 07:44:56 +0100 Subject: [PATCH 109/475] [HttpKernel] Rename HttpStatus atribute to WithHttpStatus --- .../Attribute/{HttpStatus.php => WithHttpStatus.php} | 2 +- src/Symfony/Component/HttpKernel/CHANGELOG.md | 2 +- .../Component/HttpKernel/EventListener/ErrorListener.php | 6 +++--- .../HttpKernel/Tests/EventListener/ErrorListenerTest.php | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) rename src/Symfony/Component/HttpKernel/Attribute/{HttpStatus.php => WithHttpStatus.php} (96%) diff --git a/src/Symfony/Component/HttpKernel/Attribute/HttpStatus.php b/src/Symfony/Component/HttpKernel/Attribute/WithHttpStatus.php similarity index 96% rename from src/Symfony/Component/HttpKernel/Attribute/HttpStatus.php rename to src/Symfony/Component/HttpKernel/Attribute/WithHttpStatus.php index a2811150e07e7..718427aacc761 100644 --- a/src/Symfony/Component/HttpKernel/Attribute/HttpStatus.php +++ b/src/Symfony/Component/HttpKernel/Attribute/WithHttpStatus.php @@ -15,7 +15,7 @@ * @author Dejan Angelov */ #[\Attribute(\Attribute::TARGET_CLASS)] -class HttpStatus +class WithHttpStatus { /** * @param array $headers diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index b24b2e4fa37d8..650bd9bc75b49 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -6,7 +6,7 @@ CHANGELOG * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead * `FileProfilerStorage` removes profiles automatically after two days - * Add `#[HttpStatus]` for defining status codes for exceptions + * Add `#[WithHttpStatus]` for defining status codes for exceptions * Use an instance of `Psr\Clock\ClockInterface` to generate the current date time in `DateTimeValueResolver` * Add `#[WithLogLevel]` for defining log levels for exceptions diff --git a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php index d131fd2fb9c0a..2e8b75afcf585 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php @@ -16,7 +16,7 @@ use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Attribute\HttpStatus; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; use Symfony\Component\HttpKernel\Attribute\WithLogLevel; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ExceptionEvent; @@ -71,10 +71,10 @@ public function logKernelException(ExceptionEvent $event) // There's no specific status code defined in the configuration for this exception if (!$throwable instanceof HttpExceptionInterface) { $class = new \ReflectionClass($throwable); - $attributes = $class->getAttributes(HttpStatus::class, \ReflectionAttribute::IS_INSTANCEOF); + $attributes = $class->getAttributes(WithHttpStatus::class, \ReflectionAttribute::IS_INSTANCEOF); if ($attributes) { - /** @var HttpStatus $instance */ + /** @var WithHttpStatus $instance */ $instance = $attributes[0]->newInstance(); $throwable = new HttpException($instance->statusCode, $throwable->getMessage(), $throwable, $instance->headers); diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php index aa878849ffa8e..64068ee5d3fa8 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php @@ -18,7 +18,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Attribute\HttpStatus; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; use Symfony\Component\HttpKernel\Attribute\WithLogLevel; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; @@ -321,7 +321,7 @@ public function handle(Request $request, $type = self::MAIN_REQUEST, $catch = tr } #[\Attribute(\Attribute::TARGET_CLASS)] -class UserProvidedHttpStatusCodeAttribute extends HttpStatus +class UserProvidedHttpStatusCodeAttribute extends WithHttpStatus { public function __construct(array $headers = []) { @@ -339,7 +339,7 @@ class WithCustomUserProvidedAttribute extends \Exception { } -#[HttpStatus( +#[WithHttpStatus( statusCode: Response::HTTP_PRECONDITION_FAILED, headers: [ 'some' => 'thing', From 6c898944d693e8b8f22767e9151dd66f8a00dd78 Mon Sep 17 00:00:00 2001 From: voodooism Date: Tue, 27 Dec 2022 17:04:54 +0300 Subject: [PATCH 110/475] [FrameworkBundle] Add `extra` attribute for HttpClient Configuration --- src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 10 ++++++++++ .../Resources/config/schema/symfony-1.0.xsd | 2 ++ .../Fixtures/php/http_client_full_default_options.php | 1 + .../php/http_client_override_default_options.php | 2 ++ .../Fixtures/xml/http_client_full_default_options.xml | 5 +++++ .../xml/http_client_override_default_options.xml | 6 ++++++ .../Fixtures/yml/http_client_full_default_options.yml | 3 +++ .../yml/http_client_override_default_options.yml | 2 ++ .../DependencyInjection/FrameworkExtensionTest.php | 8 +++++++- 10 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 443b2e2792371..706ff2d565b54 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.3 --- + * Add `extra` option for `http_client.default_options` and `http_client.scoped_client` * Add `DomCrawlerAssertionsTrait::assertSelectorCount(int $count, string $selector)` * Allow to avoid `limit` definition in a RateLimiter configuration when using the `no_limit` policy * Add `--format` option to the `debug:config` command diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 44e812e735538..aefcfe8a217fd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1725,6 +1725,11 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->variableNode('md5')->end() ->end() ->end() + ->arrayNode('extra') + ->info('Extra options for specific HTTP client') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() ->append($this->addHttpClientRetrySection()) ->end() ->end() @@ -1868,6 +1873,11 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->variableNode('md5')->end() ->end() ->end() + ->arrayNode('extra') + ->info('Extra options for specific HTTP client') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() ->append($this->addHttpClientRetrySection()) ->end() ->end() 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 d1f547f70778a..fd17176c2fe7a 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 @@ -620,6 +620,7 @@ + @@ -645,6 +646,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php index 865ddd14e1203..99fa25e498678 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php @@ -24,6 +24,7 @@ 'pin-sha256' => ['14s5erg62v1v8471g2revg48r7==', 'jsda84hjtyd4821bgfesd215bsfg5412='], 'md5' => 'sdhtb481248721thbr=', ], + 'extra' => ['foo' => ['bar' => 'baz']], ], ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php index c66ce8851b42e..9880dd8e7dc15 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php @@ -6,11 +6,13 @@ 'max_host_connections' => 4, 'default_options' => [ 'headers' => ['foo' => 'bar'], + 'extra' => ['foo' => 'bar'], ], 'scoped_clients' => [ 'foo' => [ 'base_uri' => 'http://example.com', 'headers' => ['bar' => 'baz'], + 'extra' => ['bar' => 'baz'], ], ], ], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml index e897df725a93a..8d51a883927b0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml @@ -30,6 +30,11 @@ jsda84hjtyd4821bgfesd215bsfg5412= sdhtb481248721thbr= + + + baz + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml index fdee9a9132a35..412d937444a6c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml @@ -9,9 +9,15 @@ bar + + bar + baz + + baz + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml index de9300e17f158..acd33293fbe65 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml @@ -22,3 +22,6 @@ framework: peer_fingerprint: pin-sha256: ['14s5erg62v1v8471g2revg48r7==', 'jsda84hjtyd4821bgfesd215bsfg5412='] md5: 'sdhtb481248721thbr=' + extra: + foo: + bar: 'baz' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml index 14a6915380dba..3f4af70018f6a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml @@ -4,7 +4,9 @@ framework: max_host_connections: 4 default_options: headers: {'foo': 'bar'} + extra: {'foo': 'bar'} scoped_clients: foo: base_uri: http://example.com headers: {'bar': 'baz'} + extra: {'bar': 'baz'} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index d3c24f28dcadc..0fe6555b3a7fb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1852,6 +1852,7 @@ public function testHttpClientDefaultOptions() $defaultOptions = [ 'headers' => [], 'resolve' => [], + 'extra' => [], ]; $this->assertSame([$defaultOptions, 4], $container->getDefinition('http_client')->getArguments()); @@ -1872,6 +1873,7 @@ public function testHttpClientOverrideDefaultOptions() $container = $this->createContainerFromFile('http_client_override_default_options'); $this->assertSame(['foo' => 'bar'], $container->getDefinition('http_client')->getArgument(0)['headers']); + $this->assertSame(['foo' => 'bar'], $container->getDefinition('http_client')->getArgument(0)['extra']); $this->assertSame(4, $container->getDefinition('http_client')->getArgument(1)); $this->assertSame('http://example.com', $container->getDefinition('foo')->getArgument(1)); @@ -1879,10 +1881,13 @@ public function testHttpClientOverrideDefaultOptions() 'headers' => [ 'bar' => 'baz', ], + 'extra' => [ + 'bar' => 'baz', + ], 'query' => [], 'resolve' => [], ]; - $this->assertSame($expected, $container->getDefinition('foo')->getArgument(2)); + $this->assertEquals($expected, $container->getDefinition('foo')->getArgument(2)); } public function testHttpClientRetry() @@ -1944,6 +1949,7 @@ public function testHttpClientFullDefaultOptions() 'pin-sha256' => ['14s5erg62v1v8471g2revg48r7==', 'jsda84hjtyd4821bgfesd215bsfg5412='], 'md5' => 'sdhtb481248721thbr=', ], $defaultOptions['peer_fingerprint']); + $this->assertSame(['foo' => ['bar' => 'baz']], $defaultOptions['extra']); } public function provideMailer(): array From 809f02c7e47b03122aa0055557ad341f77a1ee59 Mon Sep 17 00:00:00 2001 From: Sergey Rabochiy Date: Sat, 7 Jan 2023 11:40:16 +0700 Subject: [PATCH 111/475] Fix ParameterBagTest message with PHP 8.2 --- .../HttpFoundation/Tests/Fixtures/FooEnum.php | 17 +++++++++++++ .../HttpFoundation/Tests/InputBagTest.php | 11 ++++++--- .../HttpFoundation/Tests/ParameterBagTest.php | 24 +++++++++---------- 3 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Fixtures/FooEnum.php diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/FooEnum.php b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/FooEnum.php new file mode 100644 index 0000000000000..a6f56fba1fffc --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/FooEnum.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Fixtures; + +enum FooEnum: int +{ + case Bar = 1; +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php index ccb4779ef35dc..0d0d959a67028 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\InputBag; +use Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum; class InputBagTest extends TestCase { @@ -111,7 +112,7 @@ public function testGetEnum() { $bag = new InputBag(['valid-value' => 1]); - $this->assertSame(Foo::Bar, $bag->getEnum('valid-value', Foo::class)); + $this->assertSame(FooEnum::Bar, $bag->getEnum('valid-value', FooEnum::class)); } public function testGetEnumThrowsExceptionWithInvalidValue() @@ -119,8 +120,12 @@ public function testGetEnumThrowsExceptionWithInvalidValue() $bag = new InputBag(['invalid-value' => 2]); $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum "Symfony\Component\HttpFoundation\Tests\Foo".'); + if (\PHP_VERSION_ID >= 80200) { + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum.'); + } else { + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum "Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum".'); + } - $this->assertNull($bag->getEnum('invalid-value', Foo::class)); + $this->assertNull($bag->getEnum('invalid-value', FooEnum::class)); } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php index 43aaade7efa16..7c9a228f751a9 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum; class ParameterBagTest extends TestCase { @@ -231,10 +232,10 @@ public function testGetEnum() { $bag = new ParameterBag(['valid-value' => 1]); - $this->assertSame(Foo::Bar, $bag->getEnum('valid-value', Foo::class)); + $this->assertSame(FooEnum::Bar, $bag->getEnum('valid-value', FooEnum::class)); - $this->assertNull($bag->getEnum('invalid-key', Foo::class)); - $this->assertSame(Foo::Bar, $bag->getEnum('invalid-key', Foo::class, Foo::Bar)); + $this->assertNull($bag->getEnum('invalid-key', FooEnum::class)); + $this->assertSame(FooEnum::Bar, $bag->getEnum('invalid-key', FooEnum::class, FooEnum::Bar)); } public function testGetEnumThrowsExceptionWithNotBackingValue() @@ -242,9 +243,13 @@ public function testGetEnumThrowsExceptionWithNotBackingValue() $bag = new ParameterBag(['invalid-value' => 2]); $this->expectException(\UnexpectedValueException::class); - $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum "Symfony\Component\HttpFoundation\Tests\Foo".'); + if (\PHP_VERSION_ID >= 80200) { + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum.'); + } else { + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum "Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum".'); + } - $this->assertNull($bag->getEnum('invalid-value', Foo::class)); + $this->assertNull($bag->getEnum('invalid-value', FooEnum::class)); } public function testGetEnumThrowsExceptionWithInvalidValueType() @@ -252,13 +257,8 @@ public function testGetEnumThrowsExceptionWithInvalidValueType() $bag = new ParameterBag(['invalid-value' => ['foo']]); $this->expectException(\UnexpectedValueException::class); - $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: Symfony\Component\HttpFoundation\Tests\Foo::from(): Argument #1 ($value) must be of type int, array given.'); + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum::from(): Argument #1 ($value) must be of type int, array given.'); - $this->assertNull($bag->getEnum('invalid-value', Foo::class)); + $this->assertNull($bag->getEnum('invalid-value', FooEnum::class)); } } - -enum Foo: int -{ - case Bar = 1; -} From 83e22322ee4da85f5c1be004481cbd420ada527e Mon Sep 17 00:00:00 2001 From: Joseph Bielawski Date: Mon, 2 Jan 2023 15:43:50 +0100 Subject: [PATCH 112/475] [Notifier] Add new Symfony Notifier for PagerDuty --- .../FrameworkExtension.php | 2 + .../R 97AE esources/config/notifier_transports.php | 5 + .../Notifier/Bridge/PagerDuty/.gitattributes | 4 + .../Notifier/Bridge/PagerDuty/.gitignore | 3 + .../Notifier/Bridge/PagerDuty/CHANGELOG.md | 7 ++ .../Notifier/Bridge/PagerDuty/LICENSE | 19 ++++ .../Bridge/PagerDuty/PagerDutyOptions.php | 103 ++++++++++++++++++ .../Bridge/PagerDuty/PagerDutyTransport.php | 84 ++++++++++++++ .../PagerDuty/PagerDutyTransportFactory.php | 56 ++++++++++ .../Notifier/Bridge/PagerDuty/README.md | 23 ++++ .../Tests/PagerDutyTransportFactoryTest.php | 49 +++++++++ .../Tests/PagerDutyTransportTest.php | 48 ++++++++ .../Notifier/Bridge/PagerDuty/composer.json | 30 +++++ .../Bridge/PagerDuty/phpunit.xml.dist | 31 ++++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 2 + src/Symfony/Component/Notifier/Transport.php | 2 + 17 files changed, 472 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/PagerDuty/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index bcd8aff6932e2..05ef6c629795c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -163,6 +163,7 @@ use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\PagerDuty\PagerDutyTransportFactory; use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransportFactory; use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; @@ -2604,6 +2605,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', OrangeSmsTransportFactory::class => 'notifier.transport_factory.orange-sms', OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', + PagerDutyTransportFactory::class => 'notifier.transport_factory.pager-duty', PlivoTransportFactory::class => 'notifier.transport_factory.plivo', RingCentralTransportFactory::class => 'notifier.transport_factory.ring-central', RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 0d38a65d05163..c5e0371d933ba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -48,6 +48,7 @@ use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\PagerDuty\PagerDutyTransportFactory; use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransportFactory; use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; @@ -326,5 +327,9 @@ ->set('notifier.transport_factory.mastodon', MastodonTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.pager-duty', PagerDutyTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/.gitattributes b/src/Symfony/Component/Notifier/Bridge/PagerDuty/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/.gitignore b/src/Symfony/Component/Notifier/Bridge/PagerDuty/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/PagerDuty/CHANGELOG.md new file mode 100644 index 0000000000000..1f2c8f86cde72 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/LICENSE b/src/Symfony/Component/Notifier/Bridge/PagerDuty/LICENSE new file mode 100644 index 0000000000000..f961401699b27 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyOptions.php b/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyOptions.php new file mode 100644 index 0000000000000..774e602fe0f8c --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyOptions.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\PagerDuty; + +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author Joseph Bielawski + */ +final class PagerDutyOptions implements MessageOptionsInterface +{ + public function __construct(string $routingKey, string $eventAction, string $severity, private array $options = []) + { + if (!\in_array($eventAction, ['trigger', 'acknowledge', 'resolve'], true)) { + throw new InvalidArgumentException('Invalid "event_action" option given.'); + } + + if (!\in_array($severity, ['critical', 'warning', 'error', 'info'], true)) { + throw new InvalidArgumentException('Invalid "severity" option given.'); + } + + if ($this->options['payload']['timestamp'] ?? null) { + $timestamp = \DateTimeImmutable::createFromFormat(\DateTimeInterface::RFC3339_EXTENDED, $this->options['payload']['timestamp']); + if (false === $timestamp) { + throw new InvalidArgumentException('Timestamp date must be in "RFC3339_EXTENDED" format.'); + } + } else { + $timestamp = (new \DateTimeImmutable())->format(\DateTimeInterface::RFC3339_EXTENDED); + } + + $this->options['routing_key'] = $routingKey; + $this->options['event_action'] = $eventAction; + $this->options['payload'] = [ + 'severity' => $severity, + 'timestamp' => $timestamp, + ]; + + if ($dedupKey = $options['dedup_key'] ?? null) { + $this->options['dedup_key'] = $dedupKey; + } + + if (null === $dedupKey && \in_array($eventAction, ['acknowledge', 'resolve'], true)) { + throw new InvalidArgumentException('Option "dedup_key" must be set for event actions: "acknowledge" & "resolve".'); + } + } + + public function toArray(): array + { + return $this->options; + } + + public function getRecipientId(): ?string + { + return $this->options['routing_key']; + } + + /** + * @return $this + */ + public function attachImage(string $src, string $href = '', string $alt = ''): static + { + $this->options['images'][] = [ + 'src' => $src, + 'href' => $href ?: $src, + 'alt' => $alt, + ]; + + return $this; + } + + /** + * @return $this + */ + public function attachLink(string $href, string $text): static + { + $this->options['links'][] = [ + 'href' => $href, + 'text' => $text, + ]; + + return $this; + } + + /** + * @return $this + */ + public function attachCustomDetails(array $customDetails): static + { + $this->options['payload']['custom_details'] += $customDetails; + + return $this; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransport.php b/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransport.php new file mode 100644 index 0000000000000..4e69b12c1c808 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransport.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\PagerDuty; + +use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\PushMessage; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Joseph Bielawski + */ +final class PagerDutyTransport extends AbstractTransport +{ + public function __construct(#[\SensitiveParameter] private readonly string $token, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('pagerduty://%s', $this->getEndpoint()); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof PushMessage; + } + + protected function doSend(MessageInterface $message = null): SentMessage + { + if (!$message instanceof PushMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, PushMessage::class, $message); + } + + if (null !== $message->getOptions() && !($message->getOptions() instanceof PagerDutyOptions)) { + throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, PagerDutyOptions::class)); + } + + $body = ($opts = $message->getOptions()) ? $opts->toArray() : []; + $body['payload']['summary'] = $message->getContent(); + $body['payload']['source'] = $message->getSubject(); + + $response = $this->client->request('POST', 'https://events.pagerduty.com/v2/enqueue', [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => $this->token, + ], + 'json' => $body, + ]); + + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote PagerDuty server.', $response, 0, $e); + } + + $result = $response->toArray(false); + + if (202 !== $statusCode) { + throw new TransportException(sprintf('Unable to post the PagerDuty message: "%s".', $result['error']['message']), $response); + } + + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($result['dedup_key'] ?? $message->getRecipientId()); + + return $sentMessage; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransportFactory.php new file mode 100644 index 0000000000000..93fae27f433e3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/PagerDutyTransportFactory.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\PagerDuty; + +use Symfony\Component\Notifier\Exception\IncompleteDsnException; +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author Joseph Bielawski + */ +final class PagerDutyTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): PagerDutyTransport + { + $scheme = $dsn->getScheme(); + + if ('pagerduty' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'pagerduty', $this->getSupportedSchemes()); + } + + $apiToken = $this->getUser($dsn); + $host = $this->getHost($dsn); + + return (new PagerDutyTransport($apiToken, $this->client, $this->dispatcher))->setHost($host); + } + + protected function getSupportedSchemes(): array + { + return ['pagerduty']; + } + + private function getHost(Dsn $dsn): string + { + $host = $dsn->getHost(); + if ('default' === $host) { + throw new IncompleteDsnException('Host is not set.', $dsn->getOriginalDsn()); + } + + if (!str_ends_with($host, '.pagerduty.com')) { + throw new IncompleteDsnException('Host must be in format: "subdomain.pagerduty.com".', $dsn->getOriginalDsn()); + } + + return $host; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/README.md b/src/Symfony/Component/Notifier/Bridge/PagerDuty/README.md new file mode 100644 index 0000000000000..01547d59d1a76 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/README.md @@ -0,0 +1,23 @@ +PagerDuty Notifier +================== + +Provides [PagerDuty](https://www.pagerduty.com) integration for Symfony Notifier. + +DSN example +----------- + +``` +PAGERDUTY_DSN=pagerduty://TOKEN@SUBDOMAIN +``` + +where: + - `TOKEN` is your PagerDuty API token + - `SUBDOMAIN` is your subdomain name at pagerduty.com + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportFactoryTest.php new file mode 100644 index 0000000000000..bbc96f24b0e4b --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportFactoryTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\PagerDuty\Tests; + +use Symfony\Component\Notifier\Bridge\PagerDuty\PagerDutyTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +final class PagerDutyTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): PagerDutyTransportFactory + { + return new PagerDutyTransportFactory(); + } + + public function createProvider(): iterable + { + yield [ + 'pagerduty://subdomain.pagerduty.com', + 'pagerduty://token@subdomain.pagerduty.com', + 'pagerduty://token@subdomain.eu.pagerduty.com', + ]; + } + + public function supportsProvider(): iterable + { + yield [true, 'pagerduty://host']; + yield [false, 'somethingElse://host']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing token' => ['pagerduty://@host']; + yield 'wrong host' => ['pagerduty://token@host.com']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://token@host']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportTest.php b/src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportTest.php new file mode 100644 index 0000000000000..d03f32de0ff5f --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/Tests/PagerDutyTransportTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\PagerDuty\Tests; + +use Symfony\Component\Notifier\Bridge\PagerDuty\PagerDutyOptions; +use Symfony\Component\Notifier\Bridge\PagerDuty\PagerDutyTransport; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\PushMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +final class PagerDutyTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null): PagerDutyTransport + { + return (new PagerDutyTransport('testToken', $client ?? $this->createMock(HttpClientInterface::class)))->setHost('test.pagerduty.com'); + } + + public function toStringProvider(): iterable + { + yield ['pagerduty://test.pagerduty.com', $this->createTransport()]; + } + + public function supportedMessagesProvider(): iterable + { + yield [new PushMessage('Source', 'Summary')]; + yield [new PushMessage('Source', 'Summary', new PagerDutyOptions('e93facc04764012d7bfb002500d5d1a6', 'trigger', 'info'))]; + yield [new PushMessage('Source', 'Summary', new PagerDutyOptions('e93facc04764012d7bfb002500d5d1a6', 'acknowledge', 'info', ['dedup_key' => 'srv01/test']))]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + yield [new ChatMessage('Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/composer.json b/src/Symfony/Component/Notifier/Bridge/PagerDuty/composer.json new file mode 100644 index 0000000000000..8815e4a0d4215 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/composer.json @@ -0,0 +1,30 @@ +{ + "name": "symfony/pager-duty-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony PagerDuty Notifier Bridge", + "keywords": ["chat", "pagerduty", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Joseph Bielawski", + "email": "stloyd@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.3", + "symfony/notifier": "^6.3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\PagerDuty\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/PagerDuty/phpunit.xml.dist new file mode 100644 index 0000000000000..27af4d4b826a0 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Resources + ./Tests + ./vendor + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index bbbd56a4cd093..86aa5f3930634 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -156,6 +156,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\OvhCloud\OvhCloudTransportFactory::class, 'package' => 'symfony/ovh-cloud-notifier', ], + 'pagerduty' => [ + 'class' => Bridge\PagerDuty\PagerDutyTransportFactory::class, + 'package' => 'symfony/pager-duty-notifier', + ], 'plivo' => [ 'class' => Bridge\Plivo\PlivoTransportFactory::class, 'package' => 'symfony/plivo-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 8dd5d8d92c1a7..88209ec6c66fa 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -45,6 +45,7 @@ use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\PagerDuty\PagerDutyTransportFactory; use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransportFactory; use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; @@ -112,6 +113,7 @@ public static function setUpBeforeClass(): void OctopushTransportFactory::class => false, OneSignalTransportFactory::class => false, OvhCloudTransportFactory::class => false, + PagerDutyTransportFactory::class => false, PlivoTransportFactory::class => false, RingCentralTransportFactory::class => false, RocketChatTransportFactory::class => false, diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index 78ab723de4ab0..10b4650a57200 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -40,6 +40,7 @@ use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\PagerDuty\PagerDutyTransportFactory; use Symfony\Component\Notifier\Bridge\Plivo\PlivoTransportFactory; use Symfony\Component\Notifier\Bridge\RingCentral\RingCentralTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; @@ -108,6 +109,7 @@ final class Transport OctopushTransportFactory::class, OrangeSmsTransportFactory::class, OvhCloudTransportFactory::class, + PagerDutyTransportFactory::class, PlivoTransportFactory::class, RingCentralTransportFactory::class, RocketChatTransportFactory::class, From 3d5cd0d4f2a8fb28ffc50ba7172ec72e16b54890 Mon Sep 17 00:00:00 2001 From: Reyo Stallenberg Date: Tue, 29 Nov 2022 09:13:21 +0100 Subject: [PATCH 113/475] Fix spelling of emails Use correct spelling for email --- src/Symfony/Bridge/Twig/Mime/NotificationEmail.php | 2 +- src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php | 4 ++-- .../Resources/views/Collector/mailer.html.twig | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index be6fea5c9cc56..e9681df41c432 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -38,7 +38,7 @@ class NotificationEmail extends TemplatedEmail 'action_url' => null, 'markdown' => false, 'raw' => false, - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', ]; private bool $rendered = false; diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php index ceafea1bb6b72..6e48f2b4a5d61 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php @@ -35,7 +35,7 @@ public function test() 'markdown' => true, 'raw' => false, 'a' => 'b', - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', ], $email->getContext()); } @@ -58,7 +58,7 @@ public function testSerialize() 'markdown' => false, 'raw' => true, 'a' => 'b', - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', ], $email->getContext()); $this->assertSame('@email/example/notification/body.html.twig', $email->getHtmlTemplate()); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig index 6435cf99e102a..84f2281cd4349 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig @@ -30,7 +30,7 @@ {{ source('@WebProfiler/Icon/mailer.svg') }} - E-mails + Emails {% if events.messages|length > 0 %} {{ events.messages|length }} From fcbfbb185c94a6fecabc59194f39ba3982db3445 Mon Sep 17 00:00:00 2001 From: Romain Monteil Date: Fri, 16 Dec 2022 12:03:52 +0100 Subject: [PATCH 114/475] [FrameworkBundle] Rename service `notifier.logger_notification_listener` to `notifier.notification_logger_listener` --- UPGRADE-6.3.md | 5 +++++ src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../Bundle/FrameworkBundle/Resources/config/notifier.php | 6 +++++- .../FrameworkBundle/Resources/config/notifier_debug.php | 2 +- .../FrameworkBundle/Test/NotificationAssertionsTrait.php | 4 ++-- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index b626a000b508f..89caa851669a4 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -38,6 +38,11 @@ FrameworkBundle ``` +FrameworkBundle +--------------- + + * Deprecate the `notifier.logger_notification_listener` service, use the `notifier.notification_logger_listener` service instead + HttpKernel ---------- diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 443b2e2792371..b9d859b7a643c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Add `--format` option to the `debug:config` command * Add support to pass namespace wildcard in `framework.messenger.routing` * Deprecate `framework:exceptions` tag, unwrap it and replace `framework:exception` tags' `name` attribute by `class` + * Deprecate the `notifier.logger_notification_listener` service, use the `notifier.notification_logger_listener` service instead 6.2 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php index 2f53ff03de03d..6ce674148a878 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php @@ -126,7 +126,11 @@ ->args([service('texter.transports')]) ->tag('messenger.message_handler', ['handles' => PushMessage::class]) - ->set('notifier.logger_notification_listener', NotificationLoggerListener::class) + ->set('notifier.notification_logger_listener', NotificationLoggerListener::class) ->tag('kernel.event_subscriber') + + ->alias('notifier.logger_notification_listener', 'notifier.notification_logger_listener') + ->deprecate('symfony/framework-bundle', '6.3', 'The "%alias_id%" service is deprecated, use "notifier.notification_logger_listener" instead.') + ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_debug.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_debug.php index 6147d34e4e7eb..47eab26f936da 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_debug.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_debug.php @@ -16,7 +16,7 @@ return static function (ContainerConfigurator $container) { $container->services() ->set('notifier.data_collector', NotificationDataCollector::class) - ->args([service('notifier.logger_notification_listener')]) + ->args([service('notifier.notification_logger_listener')]) ->tag('data_collector', ['template' => '@WebProfiler/Collector/notifier.html.twig', 'id' => 'notifier']) ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php index 163c2a9719c55..30298ef04c54f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php @@ -91,8 +91,8 @@ public static function getNotifierMessage(int $index = 0, string $transportName public static function getNotificationEvents(): NotificationEvents { $container = static::getContainer(); - if ($container->has('notifier.logger_notification_listener')) { - return $container->get('notifier.logger_notification_listener')->getEvents(); + if ($container->has('notifier.notification_logger_listener')) { + return $container->get('notifier.notification_logger_listener')->getEvents(); } static::fail('A client must have Notifier enabled to make notifications assertions. Did you forget to require symfony/notifier?'); From a5e4fc25289f67045c4a7cc64d3c8974e27ec5ce Mon Sep 17 00:00:00 2001 From: Matthias Bilger Date: Fri, 6 Jan 2023 22:17:21 +0100 Subject: [PATCH 115/475] Allow Usage of ContentId in html Detect usage of Content-Id in html and mark part as related, just as it would happen with a `cid:` reference. --- src/Symfony/Component/Mime/CHANGELOG.md | 5 +++++ src/Symfony/Component/Mime/Email.php | 6 ++++-- src/Symfony/Component/Mime/Tests/EmailTest.php | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Mime/CHANGELOG.md b/src/Symfony/Component/Mime/CHANGELOG.md index 83214ee6594be..3676f8a82ef42 100644 --- a/src/Symfony/Component/Mime/CHANGELOG.md +++ b/src/Symfony/Component/Mime/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Support detection of related parts if `Content-Id` is used instead of the name + 6.2 --- diff --git a/src/Symfony/Component/Mime/Email.php b/src/Symfony/Component/Mime/Email.php index 9a60f42170e86..7fcc2aae1837b 100644 --- a/src/Symfony/Component/Mime/Email.php +++ b/src/Symfony/Component/Mime/Email.php @@ -492,14 +492,16 @@ private function prepareParts(): ?array $otherParts = $relatedParts = []; foreach ($this->attachments as $part) { foreach ($names as $name) { - if ($name !== $part->getName()) { + if ($name !== $part->getName() && (!$part->hasContentId() || $name !== $part->getContentId())) { continue; } if (isset($relatedParts[$name])) { continue 2; } - $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count); + if ($name !== $part->getContentId()) { + $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count); + } $relatedParts[$name] = $part; $part->setName($part->getContentId())->asInline(); diff --git a/src/Symfony/Component/Mime/Tests/EmailTest.php b/src/Symfony/Component/Mime/Tests/EmailTest.php index 8dba651ffb882..a0d56cbaca70c 100644 --- a/src/Symfony/Component/Mime/Tests/EmailTest.php +++ b/src/Symfony/Component/Mime/Tests/EmailTest.php @@ -409,6 +409,24 @@ public function testGenerateBodyWithTextAndHtmlAndAttachedFileAndAttachedImageRe $this->assertStringContainsString('cid:'.$parts[1]->getContentId(), $generatedHtml->getBody()); } + public function testGenerateBodyWithTextAndHtmlAndAttachedFileAndAttachedImageReferencedViaCidAndContentId() + { + [$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts(); + $e = (new Email())->from('me@example.com')->to('you@example.com'); + $e->text('text content'); + $e->addPart(new DataPart($file)); + $img = new DataPart($image, 'test.gif'); + $e->addPart($img); + $e->html($content = 'html content '); + $body = $e->getBody(); + $this->assertInstanceOf(MixedPart::class, $body); + $this->assertCount(2, $related = $body->getParts()); + $this->assertInstanceOf(RelatedPart::class, $related[0]); + $this->assertEquals($filePart, $related[1]); + $this->assertCount(2, $parts = $related[0]->getParts()); + $this->assertInstanceOf(AlternativePart::class, $parts[0]); + } + public function testGenerateBodyWithHtmlAndInlinedImageTwiceReferencedViaCid() { // inline image (twice) referenced in the HTML content From 34ddeb6231f60a964e11323b8072080b9ccfb12f Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Sun, 8 Jan 2023 12:36:30 +0100 Subject: [PATCH 116/475] [DependencyInjection][DX] Add message to install symfony/config for additional debugging information --- .../Compiler/AutowirePass.php | 16 ++++++---- .../Tests/Compiler/AutowirePassTest 741A .php | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 66a175d76c267..42a49f82f054a 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -540,15 +540,19 @@ private function createTypeNotFoundMessage(TypedReference $reference, string $la if (!$r = $this->container->getReflectionClass($type, false)) { // either $type does not exist or a parent class does not exist try { - $resource = new ClassExistenceResource($type, false); - // isFresh() will explode ONLY if a parent class/trait does not exist - $resource->isFresh(0); - $parentMsg = false; + if (class_exists(ClassExistenceResource::class)) { + $resource = new ClassExistenceResource($type, false); + // isFresh() will explode ONLY if a parent class/trait does not exist + $resource->isFresh(0); + $parentMsg = false; + } else { + $parentMsg = "couldn't be loaded. Either it was not found or it is missing a parent class or a trait"; + } } catch (\ReflectionException $e) { - $parentMsg = $e->getMessage(); + $parentMsg = sprintf('is missing a parent class (%s)', $e->getMessage()); } - $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found'); + $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ?: 'was not found'); } elseif ($reference->getAttributes()) { $message = $label; $label = sprintf('"#[Target(\'%s\')" on', $reference->getName()); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 7f29757b50d00..97a2c858eac20 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -14,7 +14,9 @@ use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Symfony\Bridge\PhpUnit\ClassExistsMock; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Compiler\AutowireAsDecoratorPass; @@ -39,6 +41,11 @@ class AutowirePassTest extends TestCase { + public static function setUpBeforeClass(): void + { + ClassExistsMock::register(AutowirePass::class); + } + public function testProcess() { $container = new ContainerBuilder(); @@ -504,6 +511,30 @@ public function testParentClassNotFoundThrowsException() } } + public function testParentClassNotFoundThrowsExceptionWithoutConfigComponent() + { + ClassExistsMock::withMockedClasses([ + ClassExistenceResource::class => false, + ]); + + $container = new ContainerBuilder(); + + $aDefinition = $container->register('a', BadParentTypeHintedArgument::class); + $aDefinition->setAutowired(true); + + $container->register(Dunglas::class, Dunglas::class); + + $pass = new AutowirePass(); + try { + $pass->process($container); + $this->fail('AutowirePass should have thrown an exception'); + } catch (AutowiringFailedException $e) { + $this->assertSame('Cannot autowire service "a": argument "$r" of method "Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\BadParentTypeHintedArgument::__construct()" has type "Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\OptionalServiceClass" but this class couldn\'t be loaded. Either it was not found or it is missing a parent class or a trait.', $e->getMessage()); + } + + ClassExistsMock::withMockedClasses([]); + } + public function testDontUseAbstractServices() { $container = new ContainerBuilder(); From 693ade3f097fc27965281648dfcf823326f3b339 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 9 Jan 2023 19:25:10 +0100 Subject: [PATCH 117/475] [PhpUnitBridge] Fix `enum_exists` mock tests --- src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php b/src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php index 8115dc1316538..21cdd290400ca 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php @@ -23,6 +23,9 @@ class EnumExistsMockTest extends TestCase { public static function setUpBeforeClass(): void { + // Require the fixture file to allow PHP to be fully aware of the enum existence + require __DIR__.'/Fixtures/ExistingEnumReal.php'; + ClassExistsMock::register(__CLASS__); } @@ -34,6 +37,11 @@ protected function setUp(): void ]); } + public static function tearDownAfterClass(): void + { + ClassExistsMock::withMockedEnums([]); + } + public function testClassExists() { $this->assertFalse(class_exists(ExistingEnum::class)); From 1acf90ee68c9fdf0ca4fec746cf8ec8a1c9d484f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 11 Jan 2023 11:16:02 +0100 Subject: [PATCH 118/475] [HttpKernel] Use TZ from clock service in DateTimeValueResolver --- .../ArgumentResolver/DateTimeValueResolver.php | 4 ++-- .../ArgumentResolver/DateTimeValueResolverTest.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php index a73a7e1b47a27..0cfd42badc974 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php @@ -73,7 +73,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): array } if (null !== $format) { - $date = $class::createFromFormat($format, $value); + $date = $class::createFromFormat($format, $value, $this->clock?->now()->getTimeZone()); if (($class::getLastErrors() ?: ['warning_count' => 0])['warning_count']) { $date = false; @@ -83,7 +83,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): array $value = '@'.$value; } try { - $date = new $class($value); + $date = new $class($value, $this->clock?->now()->getTimeZone()); } catch (\Exception) { $date = false; } diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php index c56cbeb5e335f..6529ca9f7640b 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php @@ -84,8 +84,8 @@ public function testUnsupportedArgument() */ public function testFullDate(string $timezone, bool $withClock) { - date_default_timezone_set($timezone); - $resolver = new DateTimeValueResolver($withClock ? new MockClock() : null); + date_default_timezone_set($withClock ? 'UTC' : $timezone); + $resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null); $argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null); $request = self::requestWithAttributes(['dummy' => '2012-07-21 00:00:00']); @@ -103,7 +103,7 @@ public function testFullDate(string $timezone, bool $withClock) */ public function testUnixTimestamp(string $timezone, bool $withClock) { - date_default_timezone_set($timezone); + date_default_timezone_set($withClock ? 'UTC' : $timezone); $resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null); $argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null); @@ -212,7 +212,7 @@ public function testCustomClass() */ public function testDateTimeImmutable(string $timezone, bool $withClock) { - date_default_timezone_set($timezone); + date_default_timezone_set($withClock ? 'UTC' : $timezone); $resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null); $argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null); @@ -231,7 +231,7 @@ public function testDateTimeImmutable(string $timezone, bool $withClock) */ public function testWithFormat(string $timezone, bool $withClock) { - date_default_timezone_set($timezone); + date_default_timezone_set($withClock ? 'UTC' : $timezone); $resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null); $argument = new ArgumentMetadata('dummy', \DateTimeInterface::class, false, false, null, false, [ From e6c56a3e54431f7c9a47014d4245669096f98249 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 11 Jan 2023 09:59:08 +0100 Subject: [PATCH 119/475] [WebProfilerBundle] Use a dynamic SVG favicon in the profiler --- .../Resources/views/Profiler/base.html.twig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig index 1a74431d898b5..c037c07ec94bc 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig @@ -5,7 +5,11 @@ {% block title %}Symfony Profiler{% endblock %} - + + {% set request_collector = profile is defined ? profile.collectors.request|default(null) : null %} + {% set status_code = request_collector is not null ? request_collector.statuscode|default(0) : 0 %} + {% set favicon_color = status_code > 399 ? 'b41939' : status_code > 299 ? 'af8503' : '000000' %} + {% block head %} From efc9723e3b4ba7c6876825640e6cfc0953f0a8c4 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 22 Dec 2022 16:08:01 +0100 Subject: [PATCH 120/475] [WebProfilerBundle] Improve accessibility of tabs and some links --- .../views/Collector/logger.html.twig | 16 ++++---- .../views/Profiler/base_js.html.twig | 39 ++++++++++++++----- .../views/Profiler/profiler.css.twig | 32 ++++++++------- 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index 385c80795fdd9..75bb3fe1e636e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -55,28 +55,28 @@ {% set filters = collector.filters %}
    -
      -
    • +
      +
    • + -
    • +
    • + -
    • +
    • -
    + +
    diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index 0c3bff7281946..b7f64bca30d11 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -663,23 +663,31 @@ if (typeof Sfjs === 'undefined' || typeof Sfjs.loadToolbar === 'undefined') { }, createTabs: function() { + /* the accessibility options of this component have been defined according to: */ + /* www.w3.org/WAI/ARIA/apg/example-index/tabs/tabs-manual.html */ var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])'); /* create the tab navigation for each group of tabs */ for (var i = 0; i < tabGroups.length; i++) { var tabs = tabGroups[i].querySelectorAll(':scope > .tab'); - var tabNavigation = document.createElement('ul'); + var tabNavigation = document.createElement('div'); tabNavigation.className = 'tab-navigation'; + tabNavigation.setAttribute('role', 'tablist'); var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */ for (var j = 0; j < tabs.length; j++) { var tabId = 'tab-' + i + '-' + j; var tabTitle = tabs[j].querySelector('.tab-title').innerHTML; - var tabNavigationItem = document.createElement('li'); + var tabNavigationItem = document.createElement('button'); + tabNavigationItem.classList.add('tab-control'); tabNavigationItem.setAttribute('data-tab-id', tabId); + tabNavigationItem.setAttribute('role', 'tab'); + tabNavigationItem.setAttribute('aria-controls', tabId); if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; } - if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); } + if (hasClass(tabs[j], 'disabled')) { + addClass(tabNavigationItem, 'disabled'); + } tabNavigationItem.innerHTML = tabTitle; tabNavigation.appendChild(tabNavigationItem); @@ -693,24 +701,31 @@ if (typeof Sfjs === 'undefined' || typeof Sfjs.loadToolbar === 'undefined') { /* display the active tab and add the 'click' event listeners */ for (i = 0; i < tabGroups.length; i++) { - tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation li'); + tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation .tab-control'); for (j = 0; j < tabNavigation.length; j++) { tabId = tabNavigation[j].getAttribute('data-tab-id'); - document.getElementById(tabId).querySelector('.tab-title').className = 'hidden'; + const tabPanel = document.getElementById(tabId); + tabPanel.setAttribute('role', 'tabpanel'); + tabPanel.setAttribute('aria-labelledby', tabId); + tabPanel.querySelector('.tab-title').className = 'hidden'; if (hasClass(tabNavigation[j], 'active')) { - document.getElementById(tabId).className = 'block'; + tabPanel.className = 'block'; + tabNavigation[j].setAttribute('aria-selected', 'true'); + tabNavigation[j].removeAttribute('tabindex'); } else { - document.getElementById(tabId).className = 'hidden'; + tabPanel.className = 'hidden'; + tabNavigation[j].removeAttribute('aria-selected'); + tabNavigation[j].setAttribute('tabindex', '-1'); } tabNavigation[j].addEventListener('click', function(e) { var activeTab = e.target || e.srcElement; /* needed because when the tab contains HTML contents, user can click */ - /* on any of those elements instead of their parent '
  • ' element */ - while (activeTab.tagName.toLowerCase() !== 'li') { + /* on any of those elements instead of their parent '
  • -
    - Parent text - Child text +
    +
    Parent text Child text
    +
    Child text Parent text
    +
    Parent text Child text Parent text
    +
    Child text
    +
    Child text Another child
    From be5fbced648bccd0f7a907c1b980e5fdb89211c0 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Thu, 29 Sep 2022 08:26:22 +0200 Subject: [PATCH 122/475] [DependencyInjection] Enable deprecating parameters --- .../DependencyInjection/CHANGELOG.md | 1 + .../DependencyInjection/Container.php | 6 +- .../DependencyInjection/ContainerBuilder.php | 31 ++- .../DependencyInjection/Dumper/PhpDumper.php | 39 +++- .../ParameterBag/FrozenParameterBag.php | 13 +- .../ParameterBag/ParameterBag.php | 26 ++- .../Tests/ContainerBuilderTest.php | 106 +++++++++ .../Tests/Dumper/PhpDumperTest.php | 35 +++ .../container_deprecated_parameters.php | 12 ++ .../Fixtures/php/services10_as_files.txt | 6 +- .../Tests/Fixtures/php/services9_as_files.txt | 6 +- .../php/services9_inlined_factories.txt | 6 +- .../php/services9_lazy_inlined_factories.txt | 6 +- .../php/services_deprecated_parameters.php | 110 ++++++++++ ...ervices_deprecated_parameters_as_files.txt | 202 ++++++++++++++++++ .../php/services_non_shared_lazy_as_files.txt | 6 +- .../ParameterBag/FrozenParameterBagTest.php | 27 +++ .../Tests/ParameterBag/ParameterBagTest.php | 73 +++++++ 18 files changed, 671 insertions(+), 40 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_deprecated_parameters.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deprecated_parameters.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deprecated_parameters_as_files.txt diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index df045b56f05c8..d40f439080c34 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Add support for nesting autowiring-related attributes into `#[Autowire(...)]` * Deprecate undefined and numeric keys with `service_locator` config * Fail if Target attribute does not exist during compilation + * Enable deprecating parameters with `ContainerBuilder::deprecateParameter()` 6.2 --- diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 7b4c8ccf88ae5..ff10702229dc6 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Contracts\Service\ResetInterface; @@ -82,7 +83,10 @@ public function compile() { $this->parameterBag->resolve(); - $this->parameterBag = new FrozenParameterBag($this->parameterBag->all()); + $this->parameterBag = new FrozenParameterBag( + $this->parameterBag->all(), + $this->parameterBag instanceof ParameterBag ? $this->parameterBag->allDeprecated() : [] + ); $this->compiled = true; } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 7b10016ee8539..ad7bb37837b18 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -34,6 +34,7 @@ use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; @@ -610,7 +611,15 @@ public function merge(self $container) } } $this->addAliases($container->getAliases()); - $this->getParameterBag()->add($container->getParameterBag()->all()); + $parameterBag = $this->getParameterBag(); + $otherBag = $container->getParameterBag(); + $parameterBag->add($otherBag->all()); + + if ($parameterBag instanceof ParameterBag && $otherBag instanceof ParameterBag) { + foreach ($otherBag->allDeprecated() as $name => $deprecated) { + $parameterBag->deprecate($name, ...$deprecated); + } + } if ($this->trackResources) { foreach ($container->getResources() as $resource) { @@ -626,9 +635,9 @@ public function merge(self $container) $this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name)); } - if ($this->getParameterBag() instanceof EnvPlaceholderParameterBag && $container->getParameterBag() instanceof EnvPlaceholderParameterBag) { - $envPlaceholders = $container->getParameterBag()->getEnvPlaceholders(); - $this->getParameterBag()->mergeEnvPlaceholders($container->getParameterBag()); + if ($parameterBag instanceof EnvPlaceholderParameterBag && $otherBag instanceof EnvPlaceholderParameterBag) { + $envPlaceholders = $otherBag->getEnvPlaceholders(); + $parameterBag->mergeEnvPlaceholders($otherBag); } else { $envPlaceholders = []; } @@ -689,6 +698,20 @@ public function prependExtensionConfig(string $name, array $config) array_unshift($this->extensionConfigs[$name], $config); } + /** + * Deprecates a service container parameter. + * + * @throws ParameterNotFoundException if the parameter is not defined + */ + public function deprecateParameter(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.'): void + { + if (!$this->parameterBag instanceof ParameterBag) { + throw new BadMethodCallException(sprintf('The parameter bag must be an instance of "%s" to call "%s".', ParameterBag::class, __METHOD__)); + } + + $this->parameterBag->deprecate($name, $package, $version, $message); + } + /** * Compiles the container. * diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index c276f0ea7472c..1b9b5426bc031 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -35,6 +35,7 @@ use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; use Symfony\Component\DependencyInjection\Loader\FileLoader; use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; use Symfony\Component\DependencyInjection\TypedReference; @@ -1234,6 +1235,8 @@ private function startClass(string $class, string $baseClass, bool $hasProxyClas */ class $class extends $baseClass { + private const DEPRECATED_PARAMETERS = []; + protected \$parameters = []; protected readonly \WeakReference \$ref; @@ -1242,11 +1245,9 @@ public function __construct() \$this->ref = \WeakReference::create(\$this); EOF; + $code = str_replace(" private const DEPRECATED_PARAMETERS = [];\n\n", $this->addDeprecatedParameters(), $code); if ($this->asFiles) { - $code = str_replace('$parameters = []', "\$containerDir;\n protected \$parameters = [];\n private \$buildParameters", $code); - $code = str_replace('__construct()', '__construct(array $buildParameters = [], $containerDir = __DIR__)', $code); - $code .= " \$this->buildParameters = \$buildParameters;\n"; - $code .= " \$this->containerDir = \$containerDir;\n"; + $code = str_replace('__construct()', '__construct(private array $buildParameters = [], protected string $containerDir = __DIR__)', $code); if (null !== $this->targetDirRegex) { $code = str_replace('$parameters = []', "\$targetDir;\n protected \$parameters = []", $code); @@ -1391,6 +1392,24 @@ public function getRemovedIds(): array EOF; } + private function addDeprecatedParameters(): string + { + if (!($bag = $this->container->getParameterBag()) instanceof ParameterBag) { + return ''; + } + + if (!$deprecated = $bag->allDeprecated()) { + return ''; + } + $code = ''; + ksort($deprecated); + foreach ($deprecated as $param => $deprecation) { + $code .= ' '.$this->doExport($param).' => ['.implode(', ', array_map($this->doExport(...), $deprecation))."],\n"; + } + + return " private const DEPRECATED_PARAMETERS = [\n{$code} ];\n\n"; + } + private function addMethodMap(): string { $code = ''; @@ -1552,6 +1571,10 @@ private function addDefaultParametersMethod(): string public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null { + if (isset(self::DEPRECATED_PARAMETERS[$name])) { + trigger_deprecation(...self::DEPRECATED_PARAMETERS[$name]); + } + if (isset($this->buildParameters[$name])) { return $this->buildParameters[$name]; } @@ -1590,17 +1613,23 @@ public function getParameterBag(): ParameterBagInterface foreach ($this->buildParameters as $name => $value) { $parameters[$name] = $value; } - $this->parameterBag = new FrozenParameterBag($parameters); + $this->parameterBag = new FrozenParameterBag($parameters, self::DEPRECATED_PARAMETERS); } return $this->parameterBag; } EOF; + if (!$this->asFiles) { $code = preg_replace('/^.*buildParameters.*\n.*\n.*\n\n?/m', '', $code); } + if (!($bag = $this->container->getParameterBag()) instanceof ParameterBag || !$bag->allDeprecated()) { + $code = preg_replace("/\n.*DEPRECATED_PARAMETERS.*\n.*\n.*\n/m", '', $code, 1); + $code = str_replace(', self::DEPRECATED_PARAMETERS', '', $code); + } + if ($dynamicPhp) { $loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8); $getDynamicParameter = <<<'EOF' diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php b/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php index 4e0f4bc2e929d..d4933af33e8aa 100644 --- a/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php +++ b/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php @@ -25,11 +25,11 @@ class FrozenParameterBag extends ParameterBag * all keys are already lowercased. * * This is always the case when used internally. - * - * @param array $parameters An array of parameters */ - public function __construct(array $parameters = []) - { + public function __construct( + array $parameters = [], + protected array $deprecatedParameters = [], + ) { $this->parameters = $parameters; $this->resolved = true; } @@ -49,6 +49,11 @@ public function set(string $name, array|bool|string|int|float|\UnitEnum|null $va throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); } + public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.') + { + throw new LogicException('Impossible to call deprecate() on a frozen ParameterBag.'); + } + public function remove(string $name) { throw new LogicException('Impossible to call remove() on a frozen ParameterBag.'); diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php b/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php index ece5c3f45cdde..97656011d3fbd 100644 --- a/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php +++ b/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php @@ -24,6 +24,7 @@ class ParameterBag implements ParameterBagInterface { protected $parameters = []; protected $resolved = false; + protected array $deprecatedParameters = []; public function __construct(array $parameters = []) { @@ -47,6 +48,11 @@ public function all(): array return $this->parameters; } + public function allDeprecated(): array + { + return $this->deprecatedParameters; + } + public function get(string $name): array|bool|string|int|float|\UnitEnum|null { if (!\array_key_exists($name, $this->parameters)) { @@ -81,6 +87,10 @@ public function get(string $name): array|bool|string|int|float|\UnitEnum|null throw new ParameterNotFoundException($name, null, null, null, $alternatives, $nonNestedAlternative); } + if (isset($this->deprecatedParameters[$name])) { + trigger_deprecation(...$this->deprecatedParameters[$name]); + } + return $this->parameters[$name]; } @@ -95,6 +105,20 @@ public function set(string $name, array|bool|string|int|float|\UnitEnum|null $va $this->parameters[$name] = $value; } + /** + * Deprecates a service container parameter. + * + * @throws ParameterNotFoundException if the parameter is not defined + */ + public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.') + { + if (!\array_key_exists($name, $this->parameters)) { + throw new ParameterNotFoundException($name); + } + + $this->deprecatedParameters[$name] = [$package, $version, $message, $name]; + } + public function has(string $name): bool { return \array_key_exists($name, $this->parameters); @@ -102,7 +126,7 @@ public function has(string $name): bool public function remove(string $name) { - unset($this->parameters[$name]); + unset($this->parameters[$name], $this->deprecatedParameters[$name]); } public function resolve() diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index bdac922327721..aa217b832177c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -31,9 +31,11 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; @@ -42,6 +44,7 @@ use Symfony\Component\DependencyInjection\Loader\ClosureLoader; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\DependencyInjection\Tests\Compiler\Foo; @@ -100,6 +103,109 @@ public function testDefinitions() } } + /** + * The test should be kept in the group as it always expects a deprecation. + * + * @group legacy + */ + public function testDeprecateParameter() + { + $builder = new ContainerBuilder(); + $builder->setParameter('foo', 'bar'); + + $builder->deprecateParameter('foo', 'symfony/test', '6.3'); + + $this->expectDeprecation('Since symfony/test 6.3: The parameter "foo" is deprecated.'); + + $builder->getParameter('foo'); + } + + /** + * The test should be kept in the group as it always expects a deprecation. + * + * @group legacy + */ + public function testParameterDeprecationIsTrgiggeredWhenCompiled() + { + $builder = new ContainerBuilder(); + $builder->setParameter('foo', '%bar%'); + $builder->setParameter('bar', 'baz'); + + $builder->deprecateParameter('bar', 'symfony/test', '6.3'); + + $this->expectDeprecation('Since symfony/test 6.3: The parameter "bar" is deprecated.'); + + $builder->compile(); + } + + public function testDeprecateParameterThrowsWhenParameterIsUndefined() + { + $builder = new ContainerBuilder(); + + $this->expectException(ParameterNotFoundException::class); + $this->expectExceptionMessage('You have requested a non-existent parameter "foo".'); + + $builder->deprecateParameter('foo', 'symfony/test', '6.3'); + } + + public function testDeprecateParameterThrowsWhenParameterBagIsNotInternal() + { + $builder = new ContainerBuilder(new class() implements ParameterBagInterface { + public function clear() + { + } + + public function add(array $parameters) + { + } + + public function all(): array + { + return []; + } + + public function get(string $name): array|bool|string|int|float|\UnitEnum|null + { + return null; + } + + public function remove(string $name) + { + } + + public function set(string $name, \UnitEnum|float|int|bool|array|string|null $value) + { + } + + public function has(string $name): bool + { + return false; + } + + public function resolve() + { + } + + public function resolveValue(mixed $value) + { + } + + public function escapeValue(mixed $value): mixed + { + return null; + } + + public function unescapeValue(mixed $value): mixed + { + return null; + } + }); + + $this->expectException(BadMethodCallException::class); + + $builder->deprecateParameter('foo', 'symfony/test', '6.3'); + } + public function testRegister() { $builder = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 1cb63b4a6be32..a11ec86e6d928 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -451,6 +451,41 @@ public function testDumpAutowireData() $this->assertStringEqualsFile(self::$fixturesPath.'/php/services24.php', $dumper->dump()); } + /** + * The test should be kept in the group as it always expects a deprecation. + * + * @group legacy + */ + public function testDeprecatedParameters() + { + $container = include self::$fixturesPath.'/containers/container_deprecated_parameters.php'; + + $this->expectDeprecation('Since symfony/test 6.3: The parameter "foo_class" is deprecated.'); + $container->compile(); + + $dumper = new PhpDumper($container); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_deprecated_parameters.php', $dumper->dump()); + } + + /** + * The test should be kept in the group as it always expects a deprecation. + * + * @group legacy + */ + public function testDeprecatedParametersAsFiles() + { + $container = include self::$fixturesPath.'/containers/container_deprecated_parameters.php'; + + $this->expectDeprecation('Since symfony/test 6.3: The parameter "foo_class" is deprecated.'); + $container->compile(); + + $dumper = new PhpDumper($container); + $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'inline_factories_parameter' => false, 'inline_class_loader_parameter' => false]), true); + + $this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/services_deprecated_parameters_as_files.txt', $dump); + } + public function testEnvInId() { $container = include self::$fixturesPath.'/containers/container_env_in_id.php'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_deprecated_parameters.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_deprecated_parameters.php new file mode 100644 index 0000000000000..96b0f74c59759 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_deprecated_parameters.php @@ -0,0 +1,12 @@ +setParameter('foo_class', 'FooClass\\Foo'); +$container->deprecateParameter('foo_class', 'symfony/test', '6.3'); +$container->register('foo', '%foo_class%') + ->setPublic(true) +; + +return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10_as_files.txt index b7c31e8137aa4..5cbb72f802159 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10_as_files.txt @@ -59,17 +59,13 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; */ class ProjectServiceContainer extends Container { - protected $containerDir; protected $targetDir; protected $parameters = []; - private $buildParameters; protected readonly \WeakReference $ref; - public function __construct(array $buildParameters = [], $containerDir = __DIR__) + public function __construct(private array $buildParameters = [], protected string $containerDir = __DIR__) { $this->ref = \WeakReference::create($this); - $this->buildParameters = $buildParameters; - $this->containerDir = $containerDir; $this->targetDir = \dirname($containerDir); $this->services = $this->privates = []; $this->methodMap = [ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt index 1321a66ec1bce..461787826da66 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt @@ -563,17 +563,13 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; */ class ProjectServiceContainer extends Container { - protected $containerDir; protected $targetDir; protected $parameters = []; - private $buildParameters; protected readonly \WeakReference $ref; - public function __construct(array $buildParameters = [], $containerDir = __DIR__) + public function __construct(private array $buildParameters = [], protected string $containerDir = __DIR__) { $this->ref = \WeakReference::create($this); - $this->buildParameters = $buildParameters; - $this->containerDir = $containerDir; $this->targetDir = \dirname($containerDir); $this->parameters = $this->getDefaultParameters(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt index 920e4036507a4..b561ff647f05c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt @@ -36,17 +36,13 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; */ class ProjectServiceContainer extends Container { - protected $containerDir; protected $targetDir; protected $parameters = []; - private $buildParameters; protected readonly \WeakReference $ref; - public function __construct(array $buildParameters = [], $containerDir = __DIR__) + public function __construct(private array $buildParameters = [], protected string $containerDir = __DIR__) { $this->ref = \WeakReference::create($this); - $this->buildParameters = $buildParameters; - $this->containerDir = $containerDir; $this->targetDir = \dirname($containerDir); $this->parameters = $this->getDefaultParameters(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt index 4944c13961691..965dc91661cce 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt @@ -31,17 +31,13 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; */ class ProjectServiceContainer extends Container { - protected $containerDir; protected $targetDir; protected $parameters = []; - private $buildParameters; protected readonly \WeakReference $ref; - public function __construct(array $buildParameters = [], $containerDir = __DIR__) + public function __construct(private array $buildParameters = [], protected string $containerDir = __DIR__) { $this->ref = \WeakReference::create($this); - $this->buildParameters = $buildParameters; - $this->containerDir = $containerDir; $this->targetDir = \dirname($containerDir); $this->parameters = $this->getDefaultParameters(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deprecated_parameters.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deprecated_parameters.php new file mode 100644 index 0000000000000..2ed3522f6f578 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deprecated_parameters.php @@ -0,0 +1,110 @@ + ['symfony/test', '6.3', 'The parameter "%s" is deprecated.', 'foo_class'], + ]; + + protected $parameters = []; + protected readonly \WeakReference $ref; + + public function __construct() + { + $this->ref = \WeakReference::create($this); + $this->parameters = $this->getDefaultParameters(); + + $this->services = $this->privates = []; + $this->methodMap = [ + 'foo' => 'getFooService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + /** + * Gets the public 'foo' shared service. + * + * @return \FooClass\Foo + */ + protected static function getFooService($container) + { + return $container->services['foo'] = new \FooClass\Foo(); + } + + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (isset(self::DEPRECATED_PARAMETERS[$name])) { + trigger_deprecation(...self::DEPRECATED_PARAMETERS[$name]); + } + + if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { + throw new ParameterNotFoundException($name); + } + if (isset($this->loadedDynamicParameters[$name])) { + return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + + return $this->parameters[$name]; + } + + public function hasParameter(string $name): bool + { + return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters); + } + + public function setParameter(string $name, $value): void + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + public function getParameterBag(): ParameterBagInterface + { + if (null === $this->parameterBag) { + $parameters = $this->parameters; + foreach ($this->loadedDynamicParameters as $name => $loaded) { + $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + $this->parameterBag = new FrozenParameterBag($parameters, self::DEPRECATED_PARAMETERS); + } + + return $this->parameterBag; + } + + private $loadedDynamicParameters = []; + private $dynamicParameters = []; + + private function getDynamicParameter(string $name) + { + throw new ParameterNotFoundException($name); + } + + protected function getDefaultParameters(): array + { + return [ + 'foo_class' => 'FooClass\\Foo', + ]; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deprecated_parameters_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deprecated_parameters_as_files.txt new file mode 100644 index 0000000000000..7d2af40ebda4d --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deprecated_parameters_as_files.txt @@ -0,0 +1,202 @@ +Array +( + [Container%s/getFooService.php] => services['foo'] = new \FooClass\Foo(); + } +} + + [Container%s/ProjectServiceContainer.php] => ['symfony/test', '6.3', 'The parameter "%s" is deprecated.', 'foo_class'], + ]; + + protected $targetDir; + protected $parameters = []; + protected readonly \WeakReference $ref; + + public function __construct(private array $buildParameters = [], protected string $containerDir = __DIR__) + { + $this->ref = \WeakReference::create($this); + $this->targetDir = \dirname($containerDir); + $this->parameters = $this->getDefaultParameters(); + + $this->services = $this->privates = []; + $this->fileMap = [ + 'foo' => 'getFooService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + protected function load($file, $lazyLoad = true) + { + if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) { + return $class::do($this, $lazyLoad); + } + + if ('.' === $file[-4]) { + $class = substr($class, 0, -4); + } else { + $file .= '.php'; + } + + $service = require $this->containerDir.\DIRECTORY_SEPARATOR.$file; + + return class_exists($class, false) ? $class::do($this, $lazyLoad) : $service; + } + + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (isset(self::DEPRECATED_PARAMETERS[$name])) { + trigger_deprecation(...self::DEPRECATED_PARAMETERS[$name]); + } + + if (isset($this->buildParameters[$name])) { + return $this->buildParameters[$name]; + } + + if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { + throw new ParameterNotFoundException($name); + } + if (isset($this->loadedDynamicParameters[$name])) { + return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + + return $this->parameters[$name]; + } + + public function hasParameter(string $name): bool + { + if (isset($this->buildParameters[$name])) { + return true; + } + + return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters); + } + + public function setParameter(string $name, $value): void + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + public function getParameterBag(): ParameterBagInterface + { + if (null === $this->parameterBag) { + $parameters = $this->parameters; + foreach ($this->loadedDynamicParameters as $name => $loaded) { + $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + foreach ($this->buildParameters as $name => $value) { + $parameters[$name] = $value; + } + $this->parameterBag = new FrozenParameterBag($parameters, self::DEPRECATED_PARAMETERS); + } + + return $this->parameterBag; + } + + private $loadedDynamicParameters = []; + private $dynamicParameters = []; + + private function getDynamicParameter(string $name) + { + throw new ParameterNotFoundException($name); + } + + protected function getDefaultParameters(): array + { + return [ + 'foo_class' => 'FooClass\\Foo', + ]; + } +} + + [ProjectServiceContainer.preload.php] => = 7.4 when preloading is desired + +use Symfony\Component\DependencyInjection\Dumper\Preloader; + +if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) { + return; +} + +require dirname(__DIR__, %d)%svendor/autoload.php'; +(require __DIR__.'/ProjectServiceContainer.php')->set(\Container%s\ProjectServiceContainer::class, null); +require __DIR__.'/Container%s/getFooService.php'; + +$classes = []; +$classes[] = 'FooClass\Foo'; +$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface'; + +$preloaded = Preloader::preload($classes); + + [ProjectServiceContainer.php] => '%s', + 'container.build_id' => '%s', + 'container.build_time' => %d, +], __DIR__.\DIRECTORY_SEPARATOR.'Container%s'); + +) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt index 0594c76789555..f7436c455e450 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt @@ -77,17 +77,13 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; */ class ProjectServiceContainer extends Container { - protected $containerDir; protected $targetDir; protected $parameters = []; - private $buildParameters; protected readonly \WeakReference $ref; - public function __construct(array $buildParameters = [], $containerDir = __DIR__) + public function __construct(private array $buildParameters = [], protected string $containerDir = __DIR__) { $this->ref = \WeakReference::create($this); - $this->buildParameters = $buildParameters; - $this->containerDir = $containerDir; $this->targetDir = \dirname($containerDir); $this->services = $this->privates = []; $this->fileMap = [ diff --git a/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/FrozenParameterBagTest.php b/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/FrozenParameterBagTest.php index 40630364d8d3c..792a9c2455cef 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/FrozenParameterBagTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/FrozenParameterBagTest.php @@ -12,10 +12,13 @@ namespace Symfony\Component\DependencyInjection\Tests\ParameterBag; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; class FrozenParameterBagTest extends TestCase { + use ExpectDeprecationTrait; + public function testConstructor() { $parameters = [ @@ -53,4 +56,28 @@ public function testRemove() $bag = new FrozenParameterBag(['foo' => 'bar']); $bag->remove('foo'); } + + public function testDeprecate() + { + $this->expectException(\LogicException::class); + $bag = new FrozenParameterBag(['foo' => 'bar']); + $bag->deprecate('foo', 'symfony/test', '6.3'); + } + + /** + * The test should be kept in the group as it always expects a deprecation. + * + * @group legacy + */ + public function testGetDeprecated() + { + $bag = new FrozenParameterBag( + ['foo' => 'bar'], + ['foo' => ['symfony/test', '6.3', 'The parameter "%s" is deprecated.', 'foo']] + ); + + $this->expectDeprecation('Since symfony/test 6.3: The parameter "foo" is deprecated.'); + + $bag->get('foo'); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/ParameterBagTest.php b/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/ParameterBagTest.php index e97ec063e52a8..201c557025e18 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/ParameterBagTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/ParameterBagTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\ParameterBag; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -19,6 +20,8 @@ class ParameterBagTest extends TestCase { + use ExpectDeprecationTrait; + public function testConstructor() { $bag = new ParameterBag($parameters = [ @@ -48,6 +51,18 @@ public function testRemove() $this->assertEquals(['bar' => 'bar'], $bag->all(), '->remove() removes a parameter'); } + public function testRemoveWithDeprecation() + { + $bag = new ParameterBag([ + 'foo' => 'foo', + 'bar' => 'bar', + ]); + $bag->deprecate('foo', 'symfony/test', '6.3'); + $bag->remove('foo'); + $this->assertEquals(['bar' => 'bar'], $bag->all(), '->remove() removes a parameter'); + $this->assertEquals([], $bag->allDeprecated()); + } + public function testGetSet() { $bag = new ParameterBag(['foo' => 'bar']); @@ -125,6 +140,64 @@ public function provideGetThrowParameterNotFoundExceptionData() ]; } + /** + * The test should be kept in the group as it always expects a deprecation. + * + * @group legacy + */ + public function testDeprecate() + { + $bag = new ParameterBag(['foo' => 'bar']); + + $bag->deprecate('foo', 'symfony/test', '6.3'); + + $this->expectDeprecation('Since symfony/test 6.3: The parameter "foo" is deprecated.'); + + $bag->get('foo'); + } + + /** + * The test should be kept in the group as it always expects a deprecation. + * + * @group legacy + */ + public function testDeprecateWithMessage() + { + $bag = new ParameterBag(['foo' => 'bar']); + + $bag->deprecate('foo', 'symfony/test', '6.3', 'The parameter "%s" is deprecated, use "new_foo" instead.'); + + $this->expectDeprecation('Since symfony/test 6.3: The parameter "foo" is deprecated, use "new_foo" instead.'); + + $bag->get('foo'); + } + + /** + * The test should be kept in the group as it always expects a deprecation. + * + * @group legacy + */ + public function testDeprecationIsTriggeredWhenResolved() + { + $bag = new ParameterBag(['foo' => '%bar%', 'bar' => 'baz']); + + $bag->deprecate('bar', 'symfony/test', '6.3'); + + $this->expectDeprecation('Since symfony/test 6.3: The parameter "bar" is deprecated.'); + + $bag->resolve(); + } + + public function testDeprecateThrowsWhenParameterIsUndefined() + { + $bag = new ParameterBag(); + + $this->expectException(ParameterNotFoundException::class); + $this->expectExceptionMessage('You have requested a non-existent parameter "foo".'); + + $bag->deprecate('foo', 'symfony/test', '6.3'); + } + public function testHas() { $bag = new ParameterBag(['foo' => 'bar']); From 30cb03f6532bfceab7d858fca0f3e13542fd27ad Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 10 Jan 2023 11:12:25 +0100 Subject: [PATCH 123/475] [FrameworkBundle] Allow setting private services with the test container --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../TestServiceContainerRealRefPass.php | 10 ++++++ .../TestServiceContainerWeakRefPass.php | 6 ++-- .../FrameworkBundle/Test/TestContainer.php | 36 +++++++++++-------- .../Tests/Functional/KernelTestCaseTest.php | 17 +++++++++ 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 0fc2874be042b..3179f5ab526c1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG * Add support to pass namespace wildcard in `framework.messenger.routing` * Deprecate `framework:exceptions` tag, unwrap it and replace `framework:exception` tags' `name` attribute by `class` * Deprecate the `notifier.logger_notification_listener` service, use the `notifier.notification_logger_listener` service instead + * Allow setting private services with the test container 6.2 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php index 942eb635b26f3..8d54563c2c7d0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php @@ -30,10 +30,14 @@ public function process(ContainerBuilder $container) $privateContainer = $container->getDefinition('test.private_services_locator'); $definitions = $container->getDefinitions(); $privateServices = $privateContainer->getArgument(0); + $renamedIds = []; foreach ($privateServices as $id => $argument) { if (isset($definitions[$target = (string) $argument->getValues()[0]])) { $argument->setValues([new Reference($target)]); + if ($id !== $target) { + $renamedIds[$id] = $target; + } } else { unset($privateServices[$id]); } @@ -47,8 +51,14 @@ public function process(ContainerBuilder $container) if ($definitions[$target]->hasTag('container.private')) { $privateServices[$id] = new ServiceClosureArgument(new Reference($target)); } + + $renamedIds[$id] = $target; } $privateContainer->replaceArgument(0, $privateServices); + + if ($container->hasDefinition('test.service_container') && $renamedIds) { + $container->getDefinition('test.service_container')->setArgument(2, $renamedIds); + } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php index a68f94f7b6134..45166842b5e0d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php @@ -14,7 +14,6 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; /** @@ -30,10 +29,9 @@ public function process(ContainerBuilder $container) $privateServices = []; $definitions = $container->getDefinitions(); - $hasErrors = method_exists(Definition::class, 'hasErrors') ? 'hasErrors' : 'getErrors'; foreach ($definitions as $id => $definition) { - if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) && !$definition->$hasErrors() && !$definition->isAbstract()) { + if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) && !$definition->hasErrors() && !$definition->isAbstract()) { $privateServices[$id] = new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); } } @@ -45,7 +43,7 @@ public function process(ContainerBuilder $container) while (isset($aliases[$target = (string) $alias])) { $alias = $aliases[$target]; } - if (isset($definitions[$target]) && !$definitions[$target]->$hasErrors() && !$definitions[$target]->isAbstract()) { + if (isset($definitions[$target]) && !$definitions[$target]->hasErrors() && !$definitions[$target]->isAbstract()) { $privateServices[$id] = new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php index 883928087ea03..933fb6de47a83 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php @@ -13,12 +13,13 @@ use Psr\Container\ContainerInterface; use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpKernel\KernelInterface; /** * A special container used in tests. This gives access to both public and - * private services. The container will not include private services that has + * private services. The container will not include private services that have * been inlined or removed. Private services will be removed when they are not * used by other services. * @@ -28,13 +29,11 @@ */ class TestContainer extends Container { - private KernelInterface $kernel; - private string $privateServicesLocatorId; - - public function __construct(KernelInterface $kernel, string $privateServicesLocatorId) - { - $this->kernel = $kernel; - $this->privateServicesLocatorId = $privateServicesLocatorId; + public function __construct( + private KernelInterface $kernel, + private string $privateServicesLocatorId, + private array $renamedIds = [], + ) { } public function compile() @@ -69,7 +68,20 @@ public function setParameter(string $name, mixed $value) public function set(string $id, mixed $service) { - $this->getPublicContainer()->set($id, $service); + $container = $this->getPublicContainer(); + $renamedId = $this->renamedIds[$id] ?? $id; + + try { + $container->set($renamedId, $service); + } catch (InvalidArgumentException $e) { + if (!str_starts_with($e->getMessage(), "The \"$renamedId\" service is private")) { + throw $e; + } + if (isset($container->privates[$renamedId])) { + throw new InvalidArgumentException(sprintf('The "%s" service is already initialized, you cannot replace it.', $id)); + } + $container->privates[$renamedId] = $service; + } } public function has(string $id): bool @@ -104,11 +116,7 @@ public function getRemovedIds(): array private function getPublicContainer(): Container { - if (null === $container = $this->kernel->getContainer()) { - throw new \LogicException('Cannot access the container on a non-booted kernel. Did you forget to boot it?'); - } - - return $container; + return $this->kernel->getContainer() ?? throw new \LogicException('Cannot access the container on a non-booted kernel. Did you forget to boot it?'); } private function getPrivateContainer(): ContainerInterface diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php index 32bee3b587309..aa12467829ed1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php @@ -17,6 +17,7 @@ use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PublicService; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\UnusedPrivateService; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; class KernelTestCaseTest extends AbstractWebTestCase { @@ -41,4 +42,20 @@ public function testThatPrivateServicesAreAvailableIfTestConfigIsEnabled() $this->assertTrue($container->has('private_service')); $this->assertFalse($container->has(UnusedPrivateService::class)); } + + public function testThatPrivateServicesCanBeSetIfTestConfigIsEnabled() + { + static::bootKernel(['test_case' => 'TestServiceContainer']); + + $container = static::getContainer(); + + $service = new \stdClass(); + + $container->set('private_service', $service); + $this->assertSame($service, $container->get('private_service')); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The "private_service" service is already initialized, you cannot replace it.'); + $container->set('private_service', new \stdClass()); + } } From 6d905904d77ae1fe363464a940436d8cba65b1eb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 10 Jan 2023 17:01:36 +0100 Subject: [PATCH 124/475] [HttpKernel] check preload items returned by cache warmers --- .../CacheWarmer/CacheWarmerAggregate.php | 9 +++- src/Symfony/Component/HttpKernel/Kernel.php | 2 +- .../CacheWarmer/CacheWarmerAggregateTest.php | 53 +++++++++++++------ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Compo 10000 nent/HttpKernel/CacheWarmer/CacheWarmerAggregate.php b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php index af0a2d13d1a26..c4d5375f521b7 100644 --- a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php +++ b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php @@ -93,7 +93,12 @@ public function warmUp(string $cacheDir): array continue; } - $preload[] = array_values((array) $warmer->warmUp($cacheDir)); + foreach ((array) $warmer->warmUp($cacheDir) as $item) { + if (is_dir($item) || (str_starts_with($item, \dirname($cacheDir)) && !is_file($item))) { + throw new \LogicException(sprintf('"%s::warmUp()" should return a list of files or classes but "%s" is none of them.', $warmer::class, $item)); + } + $preload[] = $item; + } } } finally { if ($collectDeprecations) { @@ -110,7 +115,7 @@ public function warmUp(string $cacheDir): array } } - return array_values(array_unique(array_merge([], ...$preload))); + return array_values(array_unique($preload)); } public function isOptional(): bool diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 3ee733f54fd67..ef3a92baf8a64 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -527,7 +527,7 @@ protected function initializeContainer() $preload = array_merge($preload, (array) $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir'))); } - if ($preload && method_exists(Preloader::class, 'append') && file_exists($preloadFile = $buildDir.'/'.$class.'.preload.php')) { + if ($preload && file_exists($preloadFile = $buildDir.'/'.$class.'.preload.php')) { Preloader::append($preloadFile, $preload); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php b/src/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php index c34cc9c400c35..563486db15664 100644 --- a/src/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php @@ -17,18 +17,6 @@ class CacheWarmerAggregateTest extends TestCase { - protected static $cacheDir; - - public static function setUpBeforeClass(): void - { - self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf_cache_warmer_dir'); - } - - public static function tearDownAfterClass(): void - { - @unlink(self::$cacheDir); - } - public function testInjectWarmersUsingConstructor() { $warmer = $this->createMock(CacheWarmerInterface::class); @@ -36,7 +24,7 @@ public function testInjectWarmersUsingConstructor() ->expects($this->once()) ->method('warmUp'); $aggregate = new CacheWarmerAggregate([$warmer]); - $aggregate->warmUp(self::$cacheDir); + $aggregate->warmUp(__DIR__); } public function testWarmupDoesCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsEnabled() @@ -51,7 +39,7 @@ public function testWarmupDoesCallWarmupOnOptionalWarmersWhenEnableOptionalWarme $aggregate = new CacheWarmerAggregate([$warmer]); $aggregate->enableOptionalWarmers(); - $aggregate->warmUp(self::$cacheDir); + $aggregate->warmUp(__DIR__); } public function testWarmupDoesNotCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsNotEnabled() @@ -66,6 +54,41 @@ public function testWarmupDoesNotCallWarmupOnOptionalWarmersWhenEnableOptionalWa ->method('warmUp'); $aggregate = new CacheWarmerAggregate([$warmer]); - $aggregate->warmUp(self::$cacheDir); + $aggregate->warmUp(__DIR__); + } + + public function testWarmupReturnsFilesOrClasses() + { + $warmer = $this->createMock(CacheWarmerInterface::class); + $warmer + ->expects($this->never()) + ->method('isOptional'); + $warmer + ->expects($this->once()) + ->method('warmUp') + ->willReturn([__CLASS__, __FILE__]); + + $aggregate = new CacheWarmerAggregate([$warmer]); + $aggregate->enableOptionalWarmers(); + + $this->assertSame([__CLASS__, __FILE__], $aggregate->warmUp(__DIR__)); + } + + public function testWarmupChecksInvalidFiles() + { + $warmer = $this->createMock(CacheWarmerInterface::class); + $warmer + ->expects($this->never()) + ->method('isOptional'); + $warmer + ->expects($this->once()) + ->method('warmUp') + ->willReturn([self::class, __DIR__]); + + $aggregate = new CacheWarmerAggregate([$warmer]); + $aggregate->enableOptionalWarmers(); + + $this->expectException(\LogicException::class); + $aggregate->warmUp(__DIR__); } } From 9af8ccfc03eae20ecac4c8ccc9deae7912c41aaf Mon Sep 17 00:00:00 2001 From: "Phil E. Taylor" Date: Thu, 12 Jan 2023 10:30:38 +0000 Subject: [PATCH 125/475] [Messenger] Allow password in redis dsn when using sockets --- .../Redis/Tests/Transport/ConnectionTest.php | 61 +++++++++++++++++++ .../Bridge/Redis/Transport/Connection.php | 27 ++++++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php index 12635ad14b590..07bc9039431be 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php @@ -407,4 +407,65 @@ public function testInvalidSentinelMasterName() Connection::fromDsn(sprintf('%s/messenger-clearlasterror', $master), ['delete_after_ack' => true, 'sentinel_master' => $uid], null); } + + public function testFromDsnOnUnixSocketWithUserAndPassword() + { + $redis = $this->createMock(\Redis::class); + + $redis->expects($this->exactly(1))->method('auth') + ->with(['user', 'password']) + ->willReturn(true); + + $this->assertEquals( + new Connection([ + 'stream' => 'queue', + 'delete_after_ack' => true, + 'host' => '/var/run/redis/redis.sock', + 'port' => 0, + 'user' => 'user', + 'pass' => 'password', + ], $redis), + Connection::fromDsn('redis://user:password@/var/run/redis/redis.sock', ['stream' => 'queue', 'delete_after_ack' => true], $redis) + ); + } + + public function testFromDsnOnUnixSocketWithPassword() + { + $redis = $this->createMock(\Redis::class); + + $redis->expects($this->exactly(1))->method('auth') + ->with('password') + ->willReturn(true); + + $this->assertEquals( + new Connection([ + 'stream' => 'queue', + 'delete_after_ack' => true, + 'host' => '/var/run/redis/redis.sock', + 'port' => 0, + 'pass' => 'password', + ], $redis), + Connection::fromDsn('redis://password@/var/run/redis/redis.sock', ['stream' => 'queue', 'delete_after_ack' => true], $redis) + ); + } + + public function testFromDsnOnUnixSocketWithUser() + { + $redis = $this->createMock(\Redis::class); + + $redis->expects($this->exactly(1))->method('auth') + ->with('user') + ->willReturn(true); + + $this->assertEquals( + new Connection([ + 'stream' => 'queue', + 'delete_after_ack' => true, + 'host' => '/var/run/redis/redis.sock', + 'port' => 0, + 'user' => 'user', + ], $redis), + Connection::fromDsn('redis://user:@/var/run/redis/redis.sock', ['stream' => 'queue', 'delete_after_ack' => true], $redis) + ); + } } diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php index 6c9a8fa6e5c2f..9e21dfaecc6b5 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php @@ -23,6 +23,7 @@ * @author Robin Chalas * * @internal + * * @final */ class Connection @@ -203,13 +204,13 @@ public static function fromDsn(string $dsn, array $options = [], \Redis|\RedisCl }; } + $pass = '' !== ($parsedUrl['pass'] ?? '') ? urldecode($parsedUrl['pass']) : null; + $user = '' !== ($parsedUrl['user'] ?? '') ? urldecode($parsedUrl['user']) : null; + $options['auth'] ??= null !== $pass && null !== $user ? [$user, $pass] : ($pass ?? $user); + if (isset($parsedUrl['host'])) { - $pass = '' !== ($parsedUrl['pass'] ?? '') ? urldecode($parsedUrl['pass']) : null; - $user = '' !== ($parsedUrl['user'] ?? '') ? urldecode($parsedUrl['user']) : null; $options['host'] = $parsedUrl['host'] ?? $options['host']; $options['port'] = $parsedUrl['port'] ?? $options['port']; - // See: https://github.com/phpredis/phpredis/#auth - $options['auth'] ??= null !== $pass && null !== $user ? [$user, $pass] : ($pass ?? $user); $pathParts = explode('/', rtrim($parsedUrl['path'] ?? '', '/')); $options['stream'] = $pathParts[1] ?? $options['stream']; @@ -232,9 +233,27 @@ private static function parseDsn(string $dsn, array &$options): array $url = str_replace($scheme.':', 'file:', $dsn); } + $url = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:(?[^:@]*+):)?(?[^@]*+)@)?#', function ($m) use (&$auth) { + if (isset($m['password'])) { + if (!\in_array($m['user'], ['', 'default'], true)) { + $auth['user'] = $m['user']; + } + + $auth['pass'] = $m['password']; + } + + return 'file:'.($m[1] ?? ''); + }, $url); + if (false === $parsedUrl = parse_url($url)) { throw new InvalidArgumentException(sprintf('The given Redis DSN "%s" is invalid.', $dsn)); } + + if (null !== $auth) { + unset($parsedUrl['user']); // parse_url thinks //0@localhost/ is a username of "0"! doh! + $parsedUrl += ($auth ?? []); // But don't worry as $auth array will have user, user/pass or pass as needed + } + if (isset($parsedUrl['query'])) { parse_str($parsedUrl['query'], $dsnOptions); $options = array_merge($options, $dsnOptions); From f5802d3a2adb6eddf100cc63c016a2f76f68f49c Mon Sep 17 00:00:00 2001 From: tigitz Date: Sun, 1 Jan 2023 19:45:34 +0100 Subject: [PATCH 126/475] Leverage arrow function syntax for closure --- .../Form/ChoiceList/ORMQueryBuilderLoader.php | 8 +- .../Doctrine/Form/Type/DoctrineType.php | 16 +- .../PropertyInfo/DoctrineExtractor.php | 4 +- .../DoctrineExtensionTest.php | 4 +- .../ChoiceList/DoctrineChoiceLoaderTest.php | 4 +- .../Tests/Form/Type/EntityTypeTest.php | 36 ++--- .../PdoSessionHandlerSchemaSubscriberTest.php | 2 +- .../Tests/Handler/MailerHandlerTest.php | 12 +- .../Instantiator/RuntimeInstantiatorTest.php | 4 +- .../Bridge/Twig/Command/DebugCommand.php | 6 +- .../Bridge/Twig/Extension/CodeExtension.php | 8 +- .../Twig/Tests/Command/LintCommandTest.php | 4 +- .../AbstractBootstrap3LayoutTest.php | 8 +- .../AbstractBootstrap4LayoutTest.php | 8 +- .../Twig/Tests/Mime/BodyRendererTest.php | 4 +- .../Bridge/Twig/UndefinedCallableHandler.php | 2 +- .../AbstractPhpFileCacheWarmer.php | 2 +- .../CacheWarmer/AnnotationsCacheWarmer.php | 2 +- .../CacheWarmer/ValidatorCacheWarmer.php | 2 +- .../Command/AbstractConfigCommand.php | 4 +- .../Command/CachePoolListCommand.php | 4 +- .../Command/DebugAutowiringCommand.php | 4 +- .../Command/RouterDebugCommand.php | 4 +- .../Command/SecretsListCommand.php | 4 +- .../Command/TranslationUpdateCommand.php | 8 +- .../Command/XliffLintCommand.php | 4 +- .../Command/YamlLintCommand.php | 4 +- .../Console/Descriptor/Descriptor.php | 16 +- .../Console/Descriptor/JsonDescriptor.php | 2 +- .../Console/Descriptor/MarkdownDescriptor.php | 2 +- .../Console/Descriptor/TextDescriptor.php | 6 +- .../Console/Descriptor/XmlDescriptor.php | 2 +- .../DependencyInjection/Configuration.php | 64 ++++---- .../FrameworkExtension.php | 16 +- .../Kernel/MicroKernelTrait.php | 6 +- .../Bundle/FrameworkBundle/KernelBrowser.php | 4 +- .../Tests/Command/CachePruneCommandTest.php | 4 +- .../Tests/Command/RouterMatchCommandTest.php | 4 +- .../Console/Descriptor/ObjectsProvider.php | 4 +- .../Controller/AbstractControllerTest.php | 4 +- .../Compiler/UnusedTagsPassTest.php | 4 +- .../DependencyInjection/ConfigurationTest.php | 16 +- .../FrameworkExtensionTest.php | 4 +- .../Tests/Routing/RouterTest.php | 4 +- .../Command/DebugFirewallCommand.php | 8 +- .../Compiler/SortFirewallListenersPass.php | 4 +- .../DependencyInjection/MainConfiguration.php | 22 ++- .../Security/Factory/AccessTokenFactory.php | 8 +- .../Factory/CustomAuthenticatorFactory.php | 2 +- .../Security/Factory/RememberMeFactory.php | 4 +- .../Security/UserProvider/InMemoryFactory.php | 2 +- .../Security/UserProvider/LdapFactory.php | 2 +- .../DependencyInjection/SecurityExtension.php | 12 +- .../SecurityDataCollectorTest.php | 2 +- ...erGlobalSecurityEventListenersPassTest.php | 6 +- .../CompleteConfigurationTest.php | 2 +- .../AuthenticatorBundle/ApiAuthenticator.php | 2 +- .../FirewallAwareLoginLinkHandlerTest.php | 8 +- .../DependencyInjection/Configuration.php | 14 +- .../Configurator/EnvironmentConfigurator.php | 4 +- .../Csp/ContentSecurityPolicyHandler.php | 4 +- .../Controller/ProfilerControllerTest.php | 4 +- .../Tests/Resources/IconTest.php | 2 +- .../Cache/Tests/Adapter/AdapterTestCase.php | 6 +- .../Tests/Adapter/DoctrineDbalAdapterTest.php | 4 +- .../Tests/Adapter/MemcachedAdapterTest.php | 2 +- .../Cache/Tests/Adapter/PdoAdapterTest.php | 4 +- .../Marshaller/DefaultMarshallerTest.php | 4 +- .../EarlyExpirationDispatcherTest.php | 2 +- .../Component/Cache/Traits/ContractsTrait.php | 4 +- .../Component/Cache/Traits/RedisTrait.php | 2 +- .../Config/Builder/ConfigBuilderGenerator.php | 4 +- .../Definition/Dumper/XmlReferenceDumper.php | 4 +- .../Config/Resource/GlobResource.php | 4 +- .../Resource/ReflectionClassResource.php | 2 +- .../Builder/ArrayNodeDefinitionTest.php | 4 +- .../Definition/Builder/ExprBuilderTest.php | 8 +- .../Tests/Definition/NormalizationTest.php | 10 +- src/Symfony/Component/Console/Application.php | 10 +- .../Console/Command/CompleteCommand.php | 2 +- .../Console/Command/DumpCompletionCommand.php | 4 +- .../Component/Console/Command/HelpCommand.php | 8 +- .../Component/Console/Command/ListCommand.php | 8 +- .../Console/Descriptor/MarkdownDescriptor.php | 12 +- .../Console/Descriptor/TextDescriptor.php | 8 +- .../Component/Console/Helper/Dumper.php | 14 +- .../Component/Console/Helper/ProgressBar.php | 24 +-- .../Console/Helper/ProgressIndicator.php | 16 +- .../Console/Helper/QuestionHelper.php | 8 +- .../Console/Helper/TableCellStyle.php | 4 +- src/Symfony/Component/Console/Input/Input.php | 4 +- .../Component/Console/Question/Question.php | 4 +- .../Component/Console/Style/SymfonyStyle.php | 4 +- .../Console/Tests/ApplicationTest.php | 18 +-- .../ContainerCommandLoaderTest.php | 12 +- .../FactoryCommandLoaderTest.php | 12 +- .../Tests/Helper/ProcessHelperTest.php | 2 +- .../Console/Tests/Helper/ProgressBarTest.php | 8 +- .../Tests/Helper/QuestionHelperTest.php | 4 +- .../Helper/SymfonyQuestionHelperTest.php | 2 +- .../Console/Tests/Question/QuestionTest.php | 14 +- .../CssSelector/Node/FunctionNode.php | 4 +- .../Component/CssSelector/Parser/Parser.php | 4 +- .../CssSelector/Tests/Parser/ParserTest.php | 4 +- .../Argument/ServiceLocator.php | 2 +- .../Compiler/AutowirePass.php | 4 +- .../Compiler/PriorityTaggedServiceTrait.php | 2 +- .../ResolveInstanceofConditionalsPass.php | 2 +- .../DependencyInjection/ContainerBuilder.php | 8 +- .../DependencyInjection/Dumper/PhpDumper.php | 12 +- .../ExpressionLanguageProvider.php | 22 +-- .../Extension/ExtensionTrait.php | 2 +- .../Configurator/ContainerConfigurator.php | 2 +- .../Loader/PhpFileLoader.php | 2 +- .../Loader/XmlFileLoader.php | 2 +- .../Loader/YamlFileLoader.php | 2 +- .../DependencyInjection/ReverseContainer.php | 4 +- .../RemoveUnusedDefinitionsPassTest.php | 4 +- .../ValidateEnvPlaceholdersPassTest.php | 14 +- .../Tests/ContainerBuilderTest.php | 4 +- .../Tests/EnvVarProcessorTest.php | 44 ++--- .../RealServiceInstantiatorTest.php | 4 +- .../Tests/Loader/XmlFileLoaderTest.php | 4 +- .../Tests/Loader/YamlFileLoaderTest.php | 4 +- .../Tests/ServiceLocatorTest.php | 14 +- .../DomCrawler/Tests/AbstractCrawlerTest.php | 8 +- .../Component/Dotenv/Command/DebugCommand.php | 12 +- .../ClassNotFoundErrorEnhancer.php | 2 +- .../Component/ErrorHandler/ErrorHandler.php | 4 +- .../ErrorRenderer/HtmlErrorRenderer.php | 8 +- .../Exception/FlattenException.php | 4 +- .../Tests/DebugClassLoaderTest.php | 8 +- .../SerializerErrorRendererTest.php | 2 +- .../Tests/Exception/FlattenExceptionTest.php | 4 +- .../RegisterListenersPass.php | 2 +- .../Tests/EventDispatcherTest.php | 6 +- .../Tests/ImmutableEventDispatcherTest.php | 4 +- .../ExpressionLanguage/ExpressionFunction.php | 8 +- .../Tests/Fixtures/TestProvider.php | 6 +- .../Tests/Node/FunctionNodeTest.php | 8 +- .../Component/Filesystem/Filesystem.php | 8 +- src/Symfony/Component/Finder/Finder.php | 4 +- src/Symfony/Component/Finder/Gitignore.php | 4 +- .../Finder/Iterator/SortableIterator.php | 38 ++--- .../Iterator/VcsIgnoredFilterIterator.php | 4 +- .../Component/Finder/Tests/FinderTest.php | 12 +- .../Iterator/CustomFilterIteratorTest.php | 4 +- .../Tests/Iterator/IteratorTestCase.php | 10 +- .../Tests/Iterator/LazyIteratorTest.php | 4 +- .../Tests/Iterator/MockFileListIterator.php | 2 +- .../Tests/Iterator/SortableIteratorTest.php | 2 +- .../Form/ChoiceList/ArrayChoiceList.php | 4 +- .../Factory/DefaultChoiceListFactory.php | 16 +- .../Factory/PropertyAccessDecorator.php | 48 ++---- .../Component/Form/Command/DebugCommand.php | 4 +- .../Console/Descriptor/TextDescriptor.php | 4 +- .../Form/Extension/Core/Type/CheckboxType.php | 8 +- .../Form/Extension/Core/Type/ChoiceType.php | 16 +- .../Form/Extension/Core/Type/CountryType.php | 4 +- .../Form/Extension/Core/Type/CurrencyType.php | 4 +- .../Extension/Core/Type/DateIntervalType.php | 36 ++--- .../Form/Extension/Core/Type/DateTimeType.php | 24 +-- .../Form/Extension/Core/Type/DateType.php | 24 +-- .../Form/Extension/Core/Type/EnumType.php | 8 +- .../Form/Extension/Core/Type/FileType.php | 8 +- .../Form/Extension/Core/Type/FormType.php | 22 +-- .../Form/Extension/Core/Type/LocaleType.php | 4 +- .../Form/Extension/Core/Type/TimeType.php | 20 +-- .../Form/Extension/Core/Type/TimezoneType.php | 8 +- .../Form/Extension/Core/Type/WeekType.php | 16 +- .../DataCollector/FormDataCollector.php | 22 ++- .../Type/FormTypeValidatorExtension.php | 4 +- .../Type/RepeatedTypeValidatorExtension.php | 4 +- .../Type/UploadValidatorExtension.php | 6 +- .../Validator/ValidatorTypeGuesser.php | 12 +- src/Symfony/Component/Form/Form.php | 4 +- .../Component/Form/FormTypeGuesserChain.php | 16 +- .../Form/Tests/AbstractDivLayoutTest.php | 8 +- .../Form/Tests/CallbackTransformerTest.php | 4 +- .../Tests/ChoiceList/ArrayChoiceListTest.php | 8 +- .../Factory/Cache/ChoiceLoaderTest.php | 4 +- .../Factory/CachingFactoryDecoratorTest.php | 12 +- .../Factory/DefaultChoiceListFactoryTest.php | 114 ++++--------- .../Tests/ChoiceList/LazyChoiceListTest.php | 8 +- .../Loader/CallbackChoiceLoaderTest.php | 8 +- .../FilterChoiceLoaderDecoratorTest.php | 20 +-- .../Loader/IntlCallbackChoiceLoaderTest.php | 8 +- .../Form/Tests/Command/DebugCommandTest.php | 10 +- .../Component/Form/Tests/CompoundFormTest.php | 6 +- .../Descriptor/AbstractDescriptorTest.php | 8 +- .../Core/DataMapper/DataMapperTest.php | 4 +- .../EventListener/ResizeFormListenerTest.php | 4 +- .../Extension/Core/Type/CheckboxTypeTest.php | 8 +- .../Extension/Core/Type/ChoiceTypeTest.php | 28 +--- .../Core/Type/ChoiceTypeTranslationTest.php | 4 +- .../Core/Type/CollectionTypeTest.php | 8 +- .../Extension/Core/Type/DateTimeTypeTest.php | 4 +- .../Extension/Core/Type/DateTypeTest.php | 4 +- .../Extension/Core/Type/FormTypeTest.php | 10 +- .../Extension/Core/Type/TimeTypeTest.php | 4 +- .../DataCollector/FormDataCollectorTest.php | 4 +- .../Constraints/FormValidatorTest.php | 16 +- .../Type/UploadValidatorExtensionTest.php | 6 +- .../ViolationMapper/ViolationMapperTest.php | 2 +- .../Form/Tests/Fixtures/ArrayChoiceLoader.php | 4 +- .../Form/Tests/Fixtures/ChoiceSubType.php | 10 +- .../Fixtures/LazyChoiceTypeExtension.php | 10 +- .../Tests/Resources/TranslationFilesTest.php | 2 +- .../Component/Form/Tests/SimpleFormTest.php | 4 +- .../Component/HttpClient/CurlHttpClient.php | 16 +- .../Component/HttpClient/HttpClientTrait.php | 24 ++- .../HttpClient/Internal/AmpClientState.php | 4 +- .../HttpClient/Internal/CurlClientState.php | 4 +- .../Component/HttpClient/NativeHttpClient.php | 14 +- .../HttpClient/Response/AmpResponse.php | 4 +- .../HttpClient/Response/HttplugPromise.php | 4 +- .../HttpClient/Response/MockResponse.php | 4 +- .../HttpClient/Response/NativeResponse.php | 4 +- .../HttpClient/Tests/HttplugClientTest.php | 6 +- .../HttpClient/Tests/MockHttpClientTest.php | 24 +-- .../Tests/Response/HttplugPromiseTest.php | 2 +- .../Tests/TraceableHttpClientTest.php | 4 +- .../Component/HttpFoundation/AcceptHeader.php | 4 +- .../Component/HttpFoundation/FileBag.php | 2 +- .../Component/HttpFoundation/Request.php | 4 +- .../HttpFoundation/RequestMatcher.php | 4 +- .../RequestMatcher/IpsRequestMatcher.php | 4 +- .../RequestMatcher/MethodRequestMatcher.php | 4 +- .../RequestMatcher/SchemeRequestMatcher.php | 4 +- .../Constraint/ResponseCookieValueSame.php | 4 +- .../Test/Constraint/ResponseHasCookie.php | 4 +- .../HttpFoundation/Tests/InputBagTest.php | 4 +- .../HttpFoundation/Tests/ParameterBagTest.php | 4 +- .../AttributesRequestMatcherTest.php | 4 +- .../Tests/RequestMatcherTest.php | 4 +- .../HttpFoundation/Tests/RequestTest.php | 4 +- .../Tests/ResponseFunctionalTest.php | 2 +- .../Handler/AbstractSessionHandlerTest.php | 2 +- .../Handler/Fixtures/invalid_regenerate.php | 2 +- .../Storage/Handler/Fixtures/regenerate.php | 2 +- .../Storage/Handler/Fixtures/storage.php | 2 +- .../Handler/Fixtures/with_samesite.php | 2 +- .../Fixtures/with_samesite_and_migration.php | 2 +- .../Handler/MemcachedSessionHandlerTest.php | 2 +- .../Storage/Handler/PdoSessionHandlerTest.php | 10 +- .../Handler/SessionHandlerFactoryTest.php | 2 +- .../Controller/ControllerResolver.php | 4 +- .../DataCollector/LoggerDataCollector.php | 4 +- .../DataCollector/RequestDataCollector.php | 2 +- .../Profiler/FileProfilerStorage.php | 4 +- .../HttpKernel/Resources/welcome.html.php | 4 +- .../NotTaggedControllerValueResolverTest.php | 10 +- .../ServiceValueResolverTest.php | 40 ++--- .../Controller/ControllerResolverTest.php | 4 +- .../RequestDataCollectorTest.php | 2 +- .../Debug/TraceableEventDispatcherTest.php | 4 +- .../CacheAttributeListenerTest.php | 16 +- .../Tests/EventListener/ErrorListenerTest.php | 16 +- .../EventListener/RouterListenerTest.php | 4 +- .../HttpKernel/Tests/HttpKernelTest.php | 10 +- .../HttpKernel/Tests/Log/LoggerTest.php | 4 +- .../Data/Generator/CurrencyDataGenerator.php | 4 +- .../Data/Generator/LanguageDataGenerator.php | 4 +- .../Intl/Data/Util/LocaleScanner.php | 4 +- .../Intl/Resources/bin/update-data.php | 4 +- .../Component/Intl/Tests/CurrenciesTest.php | 10 +- .../Component/Intl/Tests/LanguagesTest.php | 8 +- .../Intl/Tests/ResourceBundleTestCase.php | 12 +- .../Component/Intl/Tests/TimezonesTest.php | 8 +- .../Intl/Tests/Util/GitRepositoryTest.php | 4 +- .../Ldap/Adapter/AbstractConnection.php | 8 +- .../Component/Ldap/Adapter/AbstractQuery.php | 4 +- .../CheckLdapCredentialsListenerTest.php | 6 +- .../Transport/SesApiAsyncAwsTransport.php | 8 +- .../Transport/MailPaceApiTransportTest.php | 14 +- .../Transport/MandrillApiTransportTest.php | 8 +- .../Transport/MandrillHttpTransportTest.php | 8 +- .../Transport/MandrillHttpTransport.php | 4 +- .../Transport/OhMySmtpApiTransportTest.php | 28 ++-- .../Transport/PostmarkApiTransportTest.php | 14 +- .../Tests/Command/MailerTestCommandTest.php | 16 +- .../Mailer/Transport/AbstractApiTransport.php | 4 +- .../Mailer/Transport/AbstractTransport.php | 4 +- .../Bridge/Amqp/Transport/Connection.php | 12 +- .../Tests/Transport/ConnectionTest.php | 16 +- .../Tests/Transport/ConnectionTest.php | 10 +- .../Bridge/Doctrine/Transport/Connection.php | 4 +- .../Transport/RedisExtIntegrationTest.php | 4 +- .../Bridge/Redis/Transport/Connection.php | 8 +- .../DataCollector/MessengerDataCollector.php | 6 +- .../DelayedMessageHandlingException.php | 4 +- .../Exception/HandlerFailedException.php | 4 +- .../Component/Messenger/HandleTrait.php | 4 +- .../Test/Middleware/MiddlewareTestCase.php | 4 +- .../Tests/Command/StatsCommandTest.php | 4 +- .../StopWorkerOnMemoryLimitListenerTest.php | 8 +- .../Tests/FailureIntegrationTest.php | 24 +-- .../Messenger/Tests/MessageBusTest.php | 16 +- .../DispatchAfterCurrentBusMiddlewareTest.php | 16 +- .../Middleware/SendMessageMiddlewareTest.php | 8 +- .../Tests/Middleware/StackMiddlewareTest.php | 4 +- .../Transport/Sender/SendersLocatorTest.php | 8 +- .../Component/Mime/Tests/EmailTest.php | 4 +- .../Chatwork/Tests/ChatworkTransportTest.php | 4 +- .../Tests/ContactEveryoneTransportTest.php | 4 +- .../Discord/Tests/DiscordTransportTest.php | 4 +- .../Esendex/Tests/EsendexTransportTest.php | 12 +- .../Firebase/Tests/FirebaseTransportTest.php | 4 +- .../Tests/GatewayApiTransportTest.php | 4 +- .../Tests/GoogleChatTransportTest.php | 12 +- .../Isendpro/Tests/IsendproTransportTest.php | 12 +- .../Tests/LineNotifyTransportTest.php | 4 +- .../LinkedIn/Tests/LinkedInTransportTest.php | 12 +- .../Mercure/Tests/MercureTransportTest.php | 4 +- .../Tests/MicrosoftTeamsTransportTest.php | 4 +- .../Tests/OneSignalTransportTest.php | 8 +- .../Tests/SendinblueTransportTest.php | 4 +- .../Bridge/Slack/Tests/SlackOptionsTest.php | 4 +- .../Bridge/Slack/Tests/SlackTransportTest.php | 16 +- .../Reply/Markup/InlineKeyboardMarkup.php | 4 +- .../Reply/Markup/ReplyKeyboardMarkup.php | 4 +- .../Telegram/Tests/TelegramTransportTest.php | 8 +- .../TurboSms/Tests/TurboSmsTransportTest.php | 8 +- .../OptionsResolver/OptionsResolver.php | 8 +- .../Tests/OptionsResolverTest.php | 152 +++++------------- src/Symfony/Component/Process/Process.php | 4 +- .../Component/Process/Tests/ProcessTest.php | 2 +- .../Extractor/PhpDocExtractor.php | 4 +- .../Extractor/PhpStanExtractor.php | 4 +- .../Routing/Generator/UrlGenerator.php | 4 +- .../Loader/AnnotationDirectoryLoader.php | 8 +- .../Routing/Loader/Psr4DirectoryLoader.php | 8 +- .../Matcher/ExpressionLanguageProvider.php | 8 +- .../Component/Routing/RouteCollection.php | 4 +- .../Tests/Fixtures/CustomXmlFileLoader.php | 2 +- .../Routing/Tests/Fixtures/glob/php_dsl.php | 4 +- .../Loader/AnnotationDirectoryLoaderTest.php | 4 +- .../ExpressionLanguageProviderTest.php | 23 +-- .../Component/Routing/Tests/RouteTest.php | 2 +- .../Component/Runtime/GenericRuntime.php | 2 +- .../Runtime/Tests/phpt/dotenv_overload.php | 4 +- ...v_overload_command_debug_exists_0_to_1.php | 8 +- ...v_overload_command_debug_exists_1_to_0.php | 8 +- .../dotenv_overload_command_env_exists.php | 8 +- .../phpt/dotenv_overload_command_no_debug.php | 8 +- .../Component/Runtime/Tests/phpt/kernel.php | 4 +- .../Component/Runtime/Tests/phpt/request.php | 4 +- .../ExpressionLanguageProvider.php | 24 +-- .../Storage/UsageTrackingTokenStorageTest.php | 4 +- .../Tests/Resources/TranslationFilesTest.php | 2 +- .../Security/Csrf/CsrfTokenManager.php | 4 +- .../Csrf/Tests/CsrfTokenManagerTest.php | 4 +- .../Authentication/AuthenticatorManager.php | 2 +- .../Authenticator/RememberMeAuthenticator.php | 4 +- .../AuthenticatorManagerTest.php | 22 ++- .../AbstractAuthenticatorTest.php | 2 +- .../Passport/Badge/UserBadgeTest.php | 2 +- .../RememberMeAuthenticatorTest.php | 4 +- .../CheckCredentialsListenerTest.php | 18 +-- .../CheckRememberMeConditionsListenerTest.php | 2 +- .../CsrfProtectionListenerTest.php | 2 +- .../PasswordMigratingListenerTest.php | 8 +- .../EventListener/RememberMeListenerTest.php | 2 +- .../SessionStrategyListenerTest.php | 2 +- .../EventListener/UserCheckerListenerTest.php | 4 +- .../Tests/Firewall/ContextListenerTest.php | 4 +- .../Tests/Firewall/SwitchUserListenerTest.php | 22 ++- .../Tests/LoginLink/LoginLinkHandlerTest.php | 32 ++-- .../PersistentRememberMeHandlerTest.php | 8 +- .../CamelCaseToSnakeCaseNameConverter.php | 4 +- .../Normalizer/ObjectNormalizer.php | 4 +- .../Tests/Annotation/ContextTest.php | 10 +- .../MetadataAwareNameConverterTest.php | 8 +- .../Features/CallbacksTestTrait.php | 8 +- .../Tests/Normalizer/ObjectNormalizerTest.php | 4 +- .../Serializer/Tests/SerializerTest.php | 68 ++++---- .../String/AbstractUnicodeString.php | 4 +- .../Resources/WcswidthDataGenerator.php | 4 +- .../Component/String/Slugger/AsciiSlugger.php | 4 +- .../String/Tests/AbstractAsciiTestCase.php | 4 +- .../String/Tests/AbstractUnicodeTestCase.php | 4 +- .../Component/String/Tests/LazyStringTest.php | 30 ++-- .../Component/Templating/PhpEngine.php | 8 +- .../Translation/Bridge/Loco/LocoProvider.php | 4 +- .../Bridge/Lokalise/LokaliseProvider.php | 4 +- .../Translation/Command/XliffLintCommand.php | 14 +- .../Resources/bin/translation-status.php | 8 +- .../Translation/TranslatableMessage.php | 4 +- .../Component/Translation/Translator.php | 4 +- .../Uid/Command/GenerateUuidCommand.php | 4 +- src/Symfony/Component/Uid/Tests/UuidTest.php | 4 +- .../Constraints/DateTimeValidator.php | 4 +- .../Constraints/TimezoneValidator.php | 4 +- .../Validator/Constraints/UniqueValidator.php | 4 +- .../DataCollector/ValidatorDataCollector.php | 16 +- .../Test/ConstraintValidatorTestCase.php | 24 +-- .../Tests/Constraints/ChoiceValidatorTest.php | 4 +- .../Tests/Constraints/UniqueValidatorTest.php | 16 +- .../Tests/Resources/TranslationFilesTest.php | 2 +- .../Validator/TraceableValidatorTest.php | 2 +- .../RecursiveContextualValidator.php | 4 +- .../Component/VarDumper/Caster/ClassStub.php | 4 +- .../VarDumper/Caster/ExceptionCaster.php | 4 +- .../VarDumper/Tests/Cloner/VarClonerTest.php | 4 +- .../Command/Descriptor/CliDescriptorTest.php | 4 +- .../Internal/LazyObjectRegistry.php | 38 ++--- .../VarExporter/Tests/LazyGhostTraitTest.php | 12 +- .../VarExporter/Tests/LazyProxyTraitTest.php | 8 +- .../EventListener/ExpressionLanguage.php | 10 +- src/Symfony/Component/Workflow/Registry.php | 4 +- .../Component/Workflow/Tests/RegistryTest.php | 4 +- .../Component/Yaml/Command/LintCommand.php | 14 +- src/Symfony/Component/Yaml/Unescaper.php | 4 +- 413 files changed, 1100 insertions(+), 2337 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index 7381a6a992679..c8b341f0edddb 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -74,16 +74,12 @@ public function getEntitiesByIds(string $identifier, array $values): array // Filter out non-integer values (e.g. ""). If we don't, some // databases such as PostgreSQL fail. - $values = array_values(array_filter($values, function ($v) { - return (string) $v === (string) (int) $v || ctype_digit($v); - })); + $values = array_values(array_filter($values, fn ($v) => (string) $v === (string) (int) $v || ctype_digit($v))); } elseif (\in_array($type, ['ulid', 'uuid', 'guid'])) { $parameterType = Connection::PARAM_STR_ARRAY; // Like above, but we just filter out empty strings. - $values = array_values(array_filter($values, function ($v) { - return '' !== (string) $v; - })); + $values = array_values(array_filter($values, fn ($v) => '' !== (string) $v)); // Convert values into right type if (Type::hasType($type)) { diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 9f7eed373df6f..68c91d157f7f0 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -193,15 +193,13 @@ public function configureOptions(OptionsResolver $resolver) // Set the "id_reader" option via the normalizer. This option is not // supposed to be set by the user. - $idReaderNormalizer = function (Options $options) { - // The ID reader is a utility that is needed to read the object IDs - // when generating the field values. The callback generating the - // field values has no access to the object manager or the class - // of the field, so we store that information in the reader. - // The reader is cached so that two choice lists for the same class - // (and hence with the same reader) can successfully be cached. - return $this->getCachedIdReader($options['em'], $options['class']); - }; + // The ID reader is a utility that is needed to read the object IDs + // when generating the field values. The callback generating the + // field values has no access to the object manager or the class + // of the field, so we store that information in the reader. + // The reader is cached so that two choice lists for the same class + // (and hence with the same reader) can successfully be cached. + $idReaderNormalizer = fn (Options $options) => $this->getCachedIdReader($options['em'], $options['class']); $resolver->setDefaults([ 'em' => null, diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index 75682577ed2fc..c567fc37fd835 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -47,9 +47,7 @@ public function getProperties(string $class, array $context = []): ?array $properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames()); if ($metadata instanceof ClassMetadataInfo && class_exists(Embedded::class) && $metadata->embeddedClasses) { - $properties = array_filter($properties, function ($property) { - return !str_contains($property, '.'); - }); + $properties = array_filter($properties, fn ($property) => !str_contains($property, '.')); $properties = array_merge($properties, array_keys($metadata->embeddedClasses)); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php index 8880455cd9077..db9398b23cb36 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -47,9 +47,7 @@ protected function setUp(): void $this->extension->expects($this->any()) ->method('getObjectManagerElementName') - ->willReturnCallback(function ($name) { - return 'doctrine.orm.'.$name; - }); + ->willReturnCallback(fn ($name) => 'doctrine.orm.'.$name); $this->extension ->method('getMappingObjectDefaultName') diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php index bdbb3e9a9b4fd..8a5eb97c1d573 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php @@ -222,7 +222,7 @@ public function testLoadValuesForChoicesDoesNotLoadIfSingleIntIdAndValueGiven() ); $choices = [$this->obj1, $this->obj2, $this->obj3]; - $value = function (\stdClass $object) { return $object->name; }; + $value = fn (\stdClass $object) => $object->name; $this->repository->expects($this->never()) ->method('findAll') @@ -367,7 +367,7 @@ public function testLoadChoicesForValuesLoadsAllIfSingleIntIdAndValueGiven() ); $choices = [$this->obj1, $this->obj2, $this->obj3]; - $value = function (\stdClass $object) { return $object->name; }; + $value = fn (\stdClass $object) => $object->name; $this->repository->expects($this->once()) ->method('findAll') diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 18f918dd3b522..f6e924af0f11e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -236,9 +236,7 @@ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function () { - return new \stdClass(); - }, + 'query_builder' => fn () => new \stdClass(), ]); $field->submit('2'); @@ -1078,10 +1076,8 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureSingle $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repository) { - return $repository->createQueryBuilder('e') - ->where('e.id IN (1, 2)'); - }, + 'query_builder' => fn (EntityRepository $repository) => $repository->createQueryBuilder('e') + ->where('e.id IN (1, 2)'), 'choice_label' => 'name', ]); @@ -1102,10 +1098,8 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureCompos $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, [ 'em' => 'default', 'class' => self::COMPOSITE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repository) { - return $repository->createQueryBuilder('e') - ->where('e.id1 IN (10, 50)'); - }, + 'query_builder' => fn (EntityRepository $repository) => $repository->createQueryBuilder('e') + ->where('e.id1 IN (10, 50)'), 'choice_label' => 'name', ]); @@ -1220,17 +1214,13 @@ public function testLoaderCaching() $formBuilder->add('property2', static::TESTED_TYPE, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repo) { - return $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'); - }, + 'query_builder' => fn (EntityRepository $repo) => $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'), ]); $formBuilder->add('property3', static::TESTED_TYPE, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repo) { - return $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'); - }, + 'query_builder' => fn (EntityRepository $repo) => $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'), ]); $form = $formBuilder->getForm(); @@ -1280,17 +1270,13 @@ public function testLoaderCachingWithParameters() $formBuilder->add('property2', static::TESTED_TYPE, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repo) { - return $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1); - }, + 'query_builder' => fn (EntityRepository $repo) => $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1), ]); $formBuilder->add('property3', static::TESTED_TYPE, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repo) { - return $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1); - }, + 'query_builder' => fn (EntityRepository $repo) => $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1), ]); $form = $formBuilder->getForm(); @@ -1791,9 +1777,7 @@ public function testWithSameLoaderAndDifferentChoiceValueCallbacks() ->add('entity_two', self::TESTED_TYPE, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'choice_value' => function ($choice) { - return $choice ? $choice->name : ''; - }, + 'choice_value' => fn ($choice) => $choice ? $choice->name : '', ]) ->createView() ; diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php index 092d8be07e892..0e1f743803526 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php @@ -34,7 +34,7 @@ public function testPostGenerateSchemaPdo() $pdoSessionHandler = $this->createMock(PdoSessionHandler::class); $pdoSessionHandler->expects($this->once()) ->method('configureSchema') - ->with($schema, fn() => true); + ->with($schema, fn () => true); $subscriber = new PdoSessionHandlerSchemaSubscriber([$pdoSessionHandler]); $subscriber->postGenerateSchema($event); diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php index 43d5ef3cfab72..b4044d9c870db 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php @@ -39,9 +39,7 @@ public function testHandle() $this->mailer ->expects($this->once()) ->method('send') - ->with($this->callback(function (Email $email) { - return 'Alert: WARNING message' === $email->getSubject() && null === $email->getHtmlBody(); - })) + ->with($this->callback(fn (Email $email) => 'Alert: WARNING message' === $email->getSubject() && null === $email->getHtmlBody())) ; $handler->handle($this->getRecord(Logger::WARNING, 'message')); } @@ -53,9 +51,7 @@ public function testHandleBatch() $this->mailer ->expects($this->once()) ->method('send') - ->with($this->callback(function (Email $email) { - return 'Alert: ERROR error' === $email->getSubject() && null === $email->getHtmlBody(); - })) + ->with($this->callback(fn (Email $email) => 'Alert: ERROR error' === $email->getSubject() && null === $email->getHtmlBody())) ; $handler->handleBatch($this->getMultipleRecords()); } @@ -86,9 +82,7 @@ public function testHtmlContent() $this->mailer ->expects($this->once()) ->method('send') - ->with($this->callback(function (Email $email) { - return 'Alert: WARNING message' === $email->getSubject() && null === $email->getTextBody(); - })) + ->with($this->callback(fn (Email $email) => 'Alert: WARNING message' === $email->getSubject() && null === $email->getTextBody())) ; $handler->handle($this->getRecord(Logger::WARNING, 'message')); } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php index fe76f50c53284..15190df1d308b 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php @@ -42,9 +42,7 @@ public function testInstantiateProxy() $instance = new \stdClass(); $container = $this->createMock(ContainerInterface::class); $definition = new Definition('stdClass'); - $instantiator = function () use ($instance) { - return $instance; - }; + $instantiator = fn () => $instance; /* @var $proxy LazyLoadingInterface|ValueHolderInterface */ $proxy = $this->instantiator->instantiateProxy($container, $definition, 'foo', $instantiator); diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index 6fae02cb9bc91..d4e8528ed536c 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -162,9 +162,7 @@ private function displayPathsText(SymfonyStyle $io, string $name) [$namespace, $shortname] = $this->parseTemplateName($name); $alternatives = $this->findAlternatives($shortname, $shortnames); if (FilesystemLoader::MAIN_NAMESPACE !== $namespace) { - $alternatives = array_map(function ($shortname) use ($namespace) { - return '@'.$namespace.'/'.$shortname; - }, $alternatives); + $alternatives = array_map(fn ($shortname) => '@'.$namespace.'/'.$shortname, $alternatives); } } @@ -543,7 +541,7 @@ private function findAlternatives(string $name, array $collection): array } $threshold = 1e3; - $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold); ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); return array_keys($alternatives); diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index dd2e0682d2901..748d60cb154b1 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -120,9 +120,7 @@ public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?stri // remove main code/span tags $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); // split multiline spans - $code = preg_replace_callback('#]++)>((?:[^<]*+
    )++[^<]*+)
    #', function ($m) { - return "".str_replace('
    ', "

    ", $m[2]).''; - }, $code); + $code = preg_replace_callback('#]++)>((?:[^<]*+
    )++[^<]*+)
    #', fn ($m) => "".str_replace('
    ', "

    ", $m[2]).'', $code); $content = explode('
    ', $code); $lines = []; @@ -188,9 +186,7 @@ public function getFileRelative(string $file): ?string public function formatFileFromText(string $text): string { - return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) { - return 'in '.$this->formatFile($match[2], $match[3]); - }, $text); + return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text); } /** diff --git a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php index 6428f4860ba78..05bef211e62be 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php @@ -159,9 +159,7 @@ private function createCommandTester(): CommandTester private function createCommand(): Command { $environment = new Environment(new FilesystemLoader(\dirname(__DIR__).'/Fixtures/templates/')); - $environment->addFilter(new TwigFilter('deprecated_filter', function ($v) { - return $v; - }, ['deprecated' => true])); + $environment->addFilter(new TwigFilter('deprecated_filter', fn ($v) => $v, ['deprecated' => true])); $command = new LintCommand($environment); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php index 808352300adf4..9f1626bd52dd8 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php @@ -1039,9 +1039,7 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => false, 'expanded' => true, ]); @@ -1404,9 +1402,7 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => true, 'expanded' => true, ]); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php index 8689df830b290..af303e49ca3e8 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php @@ -580,9 +580,7 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', ChoiceType::class, '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => false, 'expanded' => true, ]); @@ -901,9 +899,7 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', ChoiceType::class, ['&a'], [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => true, 'expanded' => true, ]); diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php index 231067e0365dc..6af152dad6c5e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php @@ -124,9 +124,7 @@ public function testRenderedOnceUnserializableContext() ; $email->textTemplate('text'); $email->context([ - 'foo' => static function () { - return 'bar'; - }, + 'foo' => static fn () => 'bar', ]); $renderer->render($email); diff --git a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php index c4d3971edcaff..9368f15b96d74 100644 --- a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php +++ b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php @@ -87,7 +87,7 @@ public static function onUndefinedFunction(string $name): TwigFunction|false } if ('webpack-encore-bundle' === self::FUNCTION_COMPONENTS[$name]) { - return new TwigFunction($name, static function () { return ''; }); + return new TwigFunction($name, static fn () => ''); } throw new SyntaxError(self::onUndefined($name, 'function', self::FUNCTION_COMPONENTS[$name])); diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php index 47b3db6d2c9d3..9409bba2e04b4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php @@ -53,7 +53,7 @@ public function warmUp(string $cacheDir): array // the ArrayAdapter stores the values serialized // to avoid mutation of the data after it was written to the cache // so here we un-serialize the values first - $values = array_map(function ($val) { return null !== $val ? unserialize($val) : null; }, $arrayAdapter->getValues()); + $values = array_map(fn ($val) => null !== $val ? unserialize($val) : null, $arrayAdapter->getValues()); return $this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()), $values); } diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php index 5ecc25b3d7078..1d94fc973b6e1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php @@ -71,7 +71,7 @@ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter): bool protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values): array { // make sure we don't cache null values - $values = array_filter($values, function ($val) { return null !== $val; }); + $values = array_filter($values, fn ($val) => null !== $val); return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); } diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php index ba0dede3948a2..6325267e2ba4c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php @@ -71,7 +71,7 @@ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter): bool protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values): array { // make sure we don't cache null values - $values = array_filter($values, function ($val) { return null !== $val; }); + $values = array_filter($values, fn ($val) => null !== $val); return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php index 5f79d95bf036d..5c392895eb31f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php @@ -35,9 +35,7 @@ protected function listBundles(OutputInterface|StyleInterface $output) $rows = []; $bundles = $this->getApplication()->getKernel()->getBundles(); - usort($bundles, function ($bundleA, $bundleB) { - return strcmp($bundleA->getName(), $bundleB->getName()); - }); + usort($bundles, fn ($bundleA, $bundleB) => strcmp($bundleA->getName(), $bundleB->getName())); foreach ($bundles as $bundle) { $extension = $bundle->getContainerExtension(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php index f1e05b0db0768..0894d3fa719e5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php @@ -51,9 +51,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $io->table(['Pool name'], array_map(function ($pool) { - return [$pool]; - }, $this->poolNames)); + $io->table(['Pool name'], array_map(fn ($pool) => [$pool], $this->poolNames)); return 0; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php index 8e5395c7a4c01..bb39b00288a99 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php @@ -77,9 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($search = $input->getArgument('search')) { $searchNormalized = preg_replace('/[^a-zA-Z0-9\x7f-\xff $]++/', '', $search); - $serviceIds = array_filter($serviceIds, function ($serviceId) use ($searchNormalized) { - return false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.'); - }); + $serviceIds = array_filter($serviceIds, fn ($serviceId) => false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.')); if (!$serviceIds) { $errorIo->error(sprintf('No autowirable classes or interfaces found matching "%s"', $search)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index a4c53beff3957..d2d1971b6ef45 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -80,9 +80,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $routes = $this->router->getRouteCollection(); $container = null; if ($this->fileLinkFormatter) { - $container = function () { - return $this->getContainerBuilder($this->getApplication()->getKernel()); - }; + $container = fn () => $this->getContainerBuilder($this->getApplication()->getKernel()); } if ($name) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php index a9d31e2217967..16a6f115992dc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php @@ -75,9 +75,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $rows = []; $dump = new Dumper($output); - $dump = static function (?string $v) use ($dump) { - return null === $v ? '******' : $dump($v); - }; + $dump = static fn (?string $v) => null === $v ? '******' : $dump($v); foreach ($secrets as $name => $value) { $rows[$name] = [$name, $dump($value)]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 426bd652ec05a..a4f34ce4dc6d9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -233,12 +233,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $list = array_merge( array_diff($allKeys, $newKeys), - array_map(function ($id) { - return sprintf('%s', $id); - }, $newKeys), - array_map(function ($id) { - return sprintf('%s', $id); - }, array_keys($operation->getObsoleteMessages($domain))) + array_map(fn ($id) => sprintf('%s', $id), $newKeys), + array_map(fn ($id) => sprintf('%s', $id), array_keys($operation->getObsoleteMessages($domain))) ); $domainMessagesCount = \count($list); diff --git a/src/Symfony/Bundle/FrameworkBundle 10000 /Command/XliffLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php index 0c33e2b8b9c84..c6c66da4fb3ac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php @@ -36,9 +36,7 @@ public function __construct() return $default($directory); }; - $isReadableProvider = function ($fileOrDirectory, $default) { - return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); - }; + $isReadableProvider = fn ($fileOrDirectory, $default) => str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php index 42c1e795ccee6..d29b9e523c478 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php @@ -35,9 +35,7 @@ public function __construct() return $default($directory); }; - $isReadableProvider = function ($fileOrDirectory, $default) { - return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); - }; + $isReadableProvider = fn ($fileOrDirectory, $default) => str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php index 64c296e813671..49c7ddea1fdfa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php @@ -241,9 +241,7 @@ protected function sortTaggedServicesByPriority(array $services): array } } } - uasort($maxPriority, function ($a, $b) { - return $b <=> $a; - }); + uasort($maxPriority, fn ($a, $b) => $b <=> $a); return array_keys($maxPriority); } @@ -260,9 +258,7 @@ protected function sortTagsByPriority(array $tags): array protected function sortByPriority(array $tag): array { - usort($tag, function ($a, $b) { - return ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0); - }); + usort($tag, fn ($a, $b) => ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0)); return $tag; } @@ -305,9 +301,7 @@ private function getContainerEnvVars(ContainerBuilder $container): array $envVars = array_unique($envVars[1]); $bag = $container->getParameterBag(); - $getDefaultParameter = function (string $name) { - return parent::get($name); - }; + $getDefaultParameter = fn (string $name) => parent::get($name); $getDefaultParameter = $getDefaultParameter->bindTo($bag, $bag::class); $getEnvReflection = new \ReflectionMethod($container, 'getEnv'); @@ -343,9 +337,7 @@ private function getContainerEnvVars(ContainerBuilder $container): array protected function getServiceEdges(ContainerBuilder $builder, string $serviceId): array { try { - return array_map(function (ServiceReferenceGraphEdge $edge) { - return $edge->getSourceNode()->getId(); - }, $builder->getCompiler()->getServiceReferenceGraph()->getNode($serviceId)->getInEdges()); + return array_map(fn (ServiceReferenceGraphEdge $edge) => $edge->getSourceNode()->getId(), $builder->getCompiler()->getServiceReferenceGraph()->getNode($serviceId)->getInEdges()); } catch (InvalidArgumentException $exception) { return []; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index 9f93badc862a9..f8970832989c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -304,7 +304,7 @@ private function getEventDispatcherListenersData(EventDispatcherInterface $event $data[] = $l; } } else { - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 7115da8cd1949..164543eebb234 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -310,7 +310,7 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev $registeredListeners = $eventDispatcher->getListeners($event); } else { // Try to see if "events" exists - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); } $this->write(sprintf('# %s', $title)."\n"); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 7bd73b8bd1bc4..04bd75c3facab 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -271,9 +271,7 @@ protected function describeContainerDefinition(Definition $definition, array $op $tagInformation = []; foreach ($tags as $tagName => $tagData) { foreach ($tagData as $tagParameters) { - $parameters = array_map(function ($key, $value) { - return sprintf('%s: %s', $key, $value); - }, array_keys($tagParameters), array_values($tagParameters)); + $parameters = array_map(fn ($key, $value) => sprintf('%s: %s', $key, $value), array_keys($tagParameters), array_values($tagParameters)); $parameters = implode(', ', $parameters); if ('' === $parameters) { @@ -496,7 +494,7 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev } else { $title .= ' Grouped by Event'; // Try to see if "events" exists - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); } $options['output']->title($title); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index 69cdcbf9a599c..0a530c61da4bd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -489,7 +489,7 @@ private function getEventDispatcherListenersDocument(EventDispatcherInterface $e $this->appendEventListenerDocument($eventDispatcher, $event, $eventDispatcherXML, $registeredListeners); } else { // Try to see if "events" exists - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 44e812e735538..38848815b75d7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -144,9 +144,7 @@ public function getConfigTreeBuilder(): TreeBuilder return ContainerBuilder::willBeAvailable($package, $class, $parentPackages); }; - $enableIfStandalone = static function (string $package, string $class) use ($willBeAvailable) { - return !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled'; - }; + $enableIfStandalone = static fn (string $package, string $class) => !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled'; $this->addCsrfSection($rootNode); $this->addFormSection($rootNode, $enableIfStandalone); @@ -693,8 +691,8 @@ private function addRequestSection(ArrayNodeDefinition $rootNode) ->useAttributeAsKey('name') ->prototype('array') ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && isset($v['mime_type']); }) - ->then(function ($v) { return $v['mime_type']; }) + ->ifTrue(fn ($v) => \is_array($v) && isset($v['mime_type'])) + ->then(fn ($v) => $v['mime_type']) ->end() ->beforeNormalization()->castToArray()->end() ->prototype('scalar')->end() @@ -1169,7 +1167,7 @@ private function addPhpErrorsSection(ArrayNodeDefinition $rootNode) }) ->end() ->validate() - ->ifTrue(function ($v) { return !(\is_int($v) || \is_bool($v) || \is_array($v)); }) + ->ifTrue(fn ($v) => !(\is_int($v) || \is_bool($v) || \is_array($v))) ->thenInvalid('The "php_errors.log" parameter should be either an integer, a boolean, or an array') ->end() ->end() @@ -1220,7 +1218,7 @@ private function addExceptionsSection(ArrayNodeDefinition $rootNode) ->scalarNode('log_level') ->info('The level of log message. Null to let Symfony decide.') ->validate() - ->ifTrue(function ($v) use ($logLevels) { return null !== $v && !\in_array($v, $logLevels, true); }) + ->ifTrue(fn ($v) => null !== $v && !\in_array($v, $logLevels, true)) ->thenInvalid(sprintf('The log level is not valid. Pick one among "%s".', implode('", "', $logLevels))) ->end() ->defaultNull() @@ -1228,11 +1226,11 @@ private function addExceptionsSection(ArrayNodeDefinition $rootNode) ->scalarNode('status_code') ->info('The status code of the response. Null or 0 to let Symfony decide.') ->beforeNormalization() - ->ifTrue(function ($v) { return 0 === $v; }) - ->then(function ($v) { return null; }) + ->ifTrue(fn ($v) => 0 === $v) + ->then(fn ($v) => null) ->end() ->validate() - ->ifTrue(function ($v) { return null !== $v && ($v < 100 || $v > 599); }) + ->ifTrue(fn ($v) => null !== $v && ($v < 100 || $v > 599)) ->thenInvalid('The status code is not valid. Pick a value between 100 and 599.') ->end() ->defaultNull() @@ -1252,14 +1250,14 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI ->info('Lock configuration') ->{$enableIfStandalone('symfony/lock', Lock::class)}() ->beforeNormalization() - ->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; }) + ->ifString()->then(fn ($v) => ['enabled' => true, 'resources' => $v]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['enabled']); }) - ->then(function ($v) { return $v + ['enabled' => true]; }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['enabled'])) + ->then(fn ($v) => $v + ['enabled' => true]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['resources']) && !isset($v['resource']); }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['resources']) && !isset($v['resource'])) ->then(function ($v) { $e = $v['enabled']; unset($v['enabled']); @@ -1269,7 +1267,7 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI ->end() ->addDefaultsIfNotSet() ->validate() - ->ifTrue(static function (array $config) { return $config['enabled'] && !$config['resources']; }) + ->ifTrue(static fn (array $config) => $config['enabled'] && !$config['resources']) ->thenInvalid('At least one resource must be defined.') ->end() ->fixXmlConfig('resource') @@ -1279,10 +1277,10 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI ->useAttributeAsKey('name') ->defaultValue(['default' => [class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphore' : 'flock']]) ->beforeNormalization() - ->ifString()->then(function ($v) { return ['default' => $v]; }) + ->ifString()->then(fn ($v) => ['default' => $v]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && array_is_list($v); }) + ->ifTrue(fn ($v) => \is_array($v) && array_is_list($v)) ->then(function ($v) { $resources = []; foreach ($v as $resource) { @@ -1297,7 +1295,7 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI ->end() ->prototype('array') ->performNoDeepMerging() - ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() ->prototype('scalar')->end() ->end() ->end() @@ -1315,14 +1313,14 @@ private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $en ->info('Semaphore configuration') ->{$enableIfStandalone('symfony/semaphore', Semaphore::class)}() ->beforeNormalization() - ->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; }) + ->ifString()->then(fn ($v) => ['enabled' => true, 'resources' => $v]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['enabled']); }) - ->then(function ($v) { return $v + ['enabled' => true]; }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['enabled'])) + ->then(fn ($v) => $v + ['enabled' => true]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['resources']) && !isset($v['resource']); }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['resources']) && !isset($v['resource'])) ->then(function ($v) { $e = $v['enabled']; unset($v['enabled']); @@ -1338,10 +1336,10 @@ private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $en ->useAttributeAsKey('name') ->requiresAtLeastOneElement() ->beforeNormalization() - ->ifString()->then(function ($v) { return ['default' => $v]; }) + ->ifString()->then(fn ($v) => ['default' => $v]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && array_is_list($v); }) + ->ifTrue(fn ($v) => \is_array($v) && array_is_list($v)) ->then(function ($v) { $resources = []; foreach ($v as $resource) { @@ -1929,9 +1927,7 @@ private function addHttpClientRetrySection() ->arrayNode('methods') ->beforeNormalization() ->ifArray() - ->then(function ($v) { - return array_map('strtoupper', $v); - }) + ->then(fn ($v) => array_map('strtoupper', $v)) ->end() ->prototype('scalar')->end() ->info('A list of HTTP methods that triggers a retry for this status code. When empty, all methods are retried') @@ -1957,7 +1953,7 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl ->info('Mailer configuration') ->{$enableIfStandalone('symfony/mailer', Mailer::class)}() ->validate() - ->ifTrue(function ($v) { return isset($v['dsn']) && \count($v['transports']); }) + ->ifTrue(fn ($v) => isset($v['dsn']) && \count($v['transports'])) ->thenInvalid('"dsn" and "transports" cannot be used together.') ->end() ->fixXmlConfig('transport') @@ -1977,9 +1973,7 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl ->performNoDeepMerging() ->beforeNormalization() ->ifArray() - ->then(function ($v) { - return array_filter(array_values($v)); - }) + ->then(fn ($v) => array_filter(array_values($v))) ->end() ->prototype('scalar')->end() ->end() @@ -1991,8 +1985,8 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl ->prototype('array') ->normalizeKeys(false) ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v) || array_keys($v) !== ['value']; }) - ->then(function ($v) { return ['value' => $v]; }) + ->ifTrue(fn ($v) => !\is_array($v) || array_keys($v) !== ['value']) + ->then(fn ($v) => ['value' => $v]) ->end() ->children() ->variableNode('value')->end() @@ -2066,7 +2060,7 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $ ->{$enableIfStandalone('symfony/rate-limiter', TokenBucketLimiter::class)}() ->fixXmlConfig('limiter') ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['limiters']) && !isset($v['limiter']); }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['limiters']) && !isset($v['limiter'])) ->then(function (array $v) { $newV = [ 'enabled' => $v['enabled'] ?? true, @@ -2117,7 +2111,7 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $ ->end() ->end() ->validate() - ->ifTrue(function ($v) { return 'no_limit' !== $v['policy'] && !isset($v['limit']); }) + ->ifTrue(fn ($v) => 'no_limit' !== $v['policy'] && !isset($v['limit'])) ->thenInvalid('A limit must be provided when using a policy different than "no_limit".') ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index bcd8aff6932e2..0f95f9c2b6074 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1013,9 +1013,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $validator = new Workflow\Validator\WorkflowValidator(); } - $trs = array_map(function (Reference $ref) use ($container): Workflow\Transition { - return $container->get((string) $ref); - }, $transitions); + $trs = array_map(fn (Reference $ref): Workflow\Transition => $container->get((string) $ref), $transitions); $realDefinition = new Workflow\Definition($places, $trs, $initialMarking); $validator->validate($realDefinition, $name); @@ -1400,9 +1398,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $finder = Finder::create() ->followLinks() ->files() - ->filter(function (\SplFileInfo $file) { - return 2 <= substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename()); - }) + ->filter(fn (\SplFileInfo $file) => 2 <= substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename())) ->in($dir) ->sortByName() ; @@ -1425,9 +1421,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder 'resource_files' => $files, 'scanned_directories' => $scannedDirectories = array_merge($dirs, $nonExistingDirs), 'cache_vary' => [ - 'scanned_directories' => array_map(static function (string $dir) use ($projectDir): string { - return str_starts_with($dir, $projectDir.'/') ? substr($dir, 1 + \strlen($projectDir)) : $dir; - }, $scannedDirectories), + 'scanned_directories' => array_map(static fn (string $dir): string => str_starts_with($dir, $projectDir.'/') ? substr($dir, 1 + \strlen($projectDir)) : $dir, $scannedDirectories), ], ] ); @@ -2143,9 +2137,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder } } - $failureTransportReferencesByTransportName = array_map(function ($failureTransportName) use ($senderReferences) { - return $senderReferences[$failureTransportName]; - }, $failureTransportsByName); + $failureTransportReferencesByTransportName = array_map(fn ($failureTransportName) => $senderReferences[$failureTransportName], $failureTransportsByName); $messageToSendersMapping = []; foreach ($config['routing'] as $message => $messageConfiguration) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php index fa04ff332433d..8069822767a47 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php @@ -168,12 +168,10 @@ public function registerContainerConfiguration(LoaderInterface $loader) /* @var ContainerPhpFileLoader $kernelLoader */ $kernelLoader = $loader->getResolver()->resolve($file); $kernelLoader->setCurrentDir(\dirname($file)); - $instanceof = &\Closure::bind(function &() { return $this->instanceof; }, $kernelLoader, $kernelLoader)(); + $instanceof = &\Closure::bind(fn &() => $this->instanceof, $kernelLoader, $kernelLoader)(); $valuePreProcessor = AbstractConfigurator::$valuePreProcessor; - AbstractConfigurator::$valuePreProcessor = function ($value) { - return $this === $value ? new Reference('kernel') : $value; - }; + AbstractConfigurator::$valuePreProcessor = fn ($value) => $this === $value ? new Reference('kernel') : $value; try { $configureContainer->getClosure($this)(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader, $container); diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index 0f4a3843fb843..b75b422ce7e5e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -132,9 +132,7 @@ public function loginUser(object $user, string $firewallContext = 'main'): stati $session->set('_security_'.$firewallContext, serialize($token)); $session->save(); - $domains = array_unique(array_map(function (Cookie $cookie) use ($session) { - return $cookie->getName() === $session->getName() ? $cookie->getDomain() : ''; - }, $this->getCookieJar()->all())) ?: ['']; + $domains = array_unique(array_map(fn (Cookie $cookie) => $cookie->getName() === $session->getName() ? $cookie->getDomain() : '', $this->getCookieJar()->all())) ?: ['']; foreach ($domains as $domain) { $cookie = new Cookie($session->getName(), $session->getId(), null, null, $domain); $this->getCookieJar()->set($cookie); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php index 983956fcb9b82..54467f1efe879 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php @@ -45,9 +45,7 @@ private function getRewindableGenerator(): RewindableGenerator private function getEmptyRewindableGenerator(): RewindableGenerator { - return new RewindableGenerator(function () { - return new \ArrayIterator([]); - }, 0); + return new RewindableGenerator(fn () => new \ArrayIterator([]), 0); } private function getKernel(): MockObject&KernelInterface diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php index f5af74b98ea5f..7dab41991b1b1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php @@ -76,9 +76,7 @@ private function getKernel() $container ->expects($this->atLeastOnce()) ->method('has') - ->willReturnCallback(function ($id) { - return 'console.command_loader' !== $id; - }) + ->willReturnCallback(fn ($id) => 'console.command_loader' !== $id) ; $container ->expects($this->any()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php index 94fcbcfa3bcd3..daafc6011d3d0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php @@ -243,7 +243,7 @@ public static function getEventDispatchers() $eventDispatcher = new EventDispatcher(); $eventDispatcher->addListener('event1', 'var_dump', 255); - $eventDispatcher->addListener('event1', function () { return 'Closure'; }, -1); + $eventDispatcher->addListener('event1', fn () => 'Closure', -1); $eventDispatcher->addListener('event2', new CallableClass()); return ['event_dispatcher_1' => $eventDispatcher]; @@ -256,7 +256,7 @@ public static function getCallables(): array 'callable_2' => ['Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\CallableClass', 'staticMethod'], 'callable_3' => [new CallableClass(), 'method'], 'callable_4' => 'Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\CallableClass::staticMethod', - 'callable_6' => function () { return 'Closure'; }, + 'callable_6' => fn () => 'Closure', 'callable_7' => new CallableClass(), 'callable_from_callable' => (new CallableClass())(...), ]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php index f5dd7703eacfb..830c604d145b1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php @@ -110,9 +110,7 @@ public function testForward() $requestStack->push($request); $kernel = $this->createMock(HttpKernelInterface::class); - $kernel->expects($this->once())->method('handle')->willReturnCallback(function (Request $request) { - return new Response($request->getRequestFormat().'--'.$request->getLocale()); - }); + $kernel->expects($this->once())->method('handle')->willReturnCallback(fn (Request $request) => new Response($request->getRequestFormat().'--'.$request->getLocale())); $container = new Container(); $container->set('request_stack', $requestStack); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php index 433b798d81a94..46dd94b86b3a4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php @@ -44,9 +44,7 @@ public function testMissingKnownTags() private function getKnownTags(): array { $tags = \Closure::bind( - static function () { - return UnusedTagsPass::KNOWN_TAGS; - }, + static fn () => UnusedTagsPass::KNOWN_TAGS, null, UnusedTagsPass::class )(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index f37794e3d7fe5..3770decba62d2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -145,15 +145,13 @@ public function testInvalidAssetsConfiguration(array $assetConfig, $expectedMess public function provideInvalidAssetConfigurationTests() { // helper to turn config into embedded package config - $createPackageConfig = function (array $packageConfig) { - return [ - 'base_urls' => '//example.com', - 'version' => 1, - 'packages' => [ - 'foo' => $packageConfig, - ], - ]; - }; + $createPackageConfig = fn (array $packageConfig) => [ + 'base_urls' => '//example.com', + 'version' => 1, + 'packages' => [ + 'foo' => $packageConfig, + ], + ]; $config = [ 'version' => 1, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index d3c24f28dcadc..eacaa38f509b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1146,9 +1146,7 @@ public function testTranslator() $nonExistingDirectories = array_filter( $options['scanned_directories'], - function ($directory) { - return !file_exists($directory); - } + fn ($directory) => !file_exists($directory) ); $this->assertNotEmpty($nonExistingDirectories, 'FrameworkBundle should pass non existing directories to Translator'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php index cdcaa490ac423..7f2fe5b7c4bb2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php @@ -647,9 +647,7 @@ private function getParameterBag(array $params = []): ContainerInterface $bag ->expects($this->any()) ->method('get') - ->willReturnCallback(function ($key) use ($params) { - return $params[$key] ?? null; - }) + ->willReturnCallback(fn ($key) => $params[$key] ?? null) ; return $bag; diff --git a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php index 220c51314d290..0f105efd5ea1b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php @@ -216,11 +216,9 @@ private function displayAuthenticators(string $name, SymfonyStyle $io): void $io->table( ['Classname'], array_map( - static function ($authenticator) { - return [ - $authenticator::class, - ]; - }, + static fn ($authenticator) => [ + $authenticator::class, + ], $authenticators ) ); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/SortFirewallListenersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/SortFirewallListenersPass.php index 6d49320445c10..7f0301a3edab7 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/SortFirewallListenersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/SortFirewallListenersPass.php @@ -45,9 +45,7 @@ private function sortFirewallContextListeners(Definition $definition, ContainerB $prioritiesByServiceId = $this->getListenerPriorities($listenerIteratorArgument, $container); $listeners = $listenerIteratorArgument->getValues(); - usort($listeners, function (Reference $a, Reference $b) use ($prioritiesByServiceId) { - return $prioritiesByServiceId[(string) $b] <=> $prioritiesByServiceId[(string) $a]; - }); + usort($listeners, fn (Reference $a, Reference $b) => $prioritiesByServiceId[(string) $b] <=> $prioritiesByServiceId[(string) $a]); $listenerIteratorArgument->setValues(array_values($listeners)); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index 44d925c1f1c0b..b748c363a9952 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -78,15 +78,15 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end() ->end() ->validate() - ->ifTrue(function ($v) { return isset($v['strategy'], $v['service']); }) + ->ifTrue(fn ($v) => isset($v['strategy'], $v['service'])) ->thenInvalid('"strategy" and "service" cannot be used together.') ->end() ->validate() - ->ifTrue(function ($v) { return isset($v['strategy'], $v['strategy_service']); }) + ->ifTrue(fn ($v) => isset($v['strategy'], $v['strategy_service'])) ->thenInvalid('"strategy" and "strategy_service" cannot be used together.') ->end() ->validate() - ->ifTrue(function ($v) { return isset($v['service'], $v['strategy_service']); }) + ->ifTrue(fn ($v) => isset($v['service'], $v['strategy_service'])) ->thenInvalid('"service" and "strategy_service" cannot be used together.') ->end() ->end() @@ -111,10 +111,10 @@ private function addRoleHierarchySection(ArrayNodeDefinition $rootNode) ->useAttributeAsKey('id') ->prototype('array') ->performNoDeepMerging() - ->beforeNormalization()->ifString()->then(function ($v) { return ['value' => $v]; })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => ['value' => $v])->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && isset($v['value']); }) - ->then(function ($v) { return preg_split('/\s*,\s*/', $v['value']); }) + ->ifTrue(fn ($v) => \is_array($v) && isset($v['value'])) + ->then(fn ($v) => preg_split('/\s*,\s*/', $v['value'])) ->end() ->prototype('scalar')->end() ->end() @@ -324,9 +324,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto $firewallNodeBuilder ->end() ->validate() - ->ifTrue(function ($v) { - return true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher']); - }) + ->ifTrue(fn ($v) => true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher'])) ->then(function ($firewall) use ($abstractFactoryKeys) { foreach ($abstractFactoryKeys as $k) { if (!isset($firewall[$k]['check_path'])) { @@ -375,7 +373,7 @@ private function addProvidersSection(ArrayNodeDefinition $rootNode) ->arrayNode('providers') ->beforeNormalization() ->ifString() - ->then(function ($v) { return preg_split('/\s*,\s*/', $v); }) + ->then(fn ($v) => preg_split('/\s*,\s*/', $v)) ->end() ->prototype('scalar')->end() ->end() @@ -393,11 +391,11 @@ private function addProvidersSection(ArrayNodeDefinition $rootNode) $providerNodeBuilder ->validate() - ->ifTrue(function ($v) { return \count($v) > 1; }) + ->ifTrue(fn ($v) => \count($v) > 1) ->thenInvalid('You cannot set multiple provider types for the same provider') ->end() ->validate() - ->ifTrue(function ($v) { return 0 === \count($v); }) + ->ifTrue(fn ($v) => 0 === \count($v)) ->thenInvalid('You must set a provider definition for the provider.') ->end() ; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php index a3ed0e3ee0839..a59a9a6f3ede0 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php @@ -46,7 +46,7 @@ public function addConfiguration(NodeDefinition $node): void ->fixXmlConfig('token_extractors') ->beforeNormalization() ->ifString() - ->then(static function (string $v): array { return [$v]; }) + ->then(static fn (string $v): array => [$v]) ->end() ->cannotBeEmpty() ->defaultValue([ @@ -97,9 +97,7 @@ private function createExtractor(ContainerBuilder $container, string $firewallNa 'request_body' => 'security.access_token_extractor.request_body', 'header' => 'security.access_token_extractor.header', ]; - $extractors = array_map(static function (string $extractor) use ($aliases): string { - return $aliases[$extractor] ?? $extractor; - }, $extractors); + $extractors = array_map(static fn (string $extractor): string => $aliases[$extractor] ?? $extractor, $extractors); if (1 === \count($extractors)) { return current($extractors); @@ -107,7 +105,7 @@ private function createExtractor(ContainerBuilder $container, string $firewallNa $extractorId = sprintf('security.authenticator.access_token.chain_extractor.%s', $firewallName); $container ->setDefinition($extractorId, new ChildDefinition('security.authenticator.access_token.chain_extractor')) - ->replaceArgument(0, array_map(function (string $extractorId): Reference {return new Reference($extractorId); }, $extractors)) + ->replaceArgument(0, array_map(fn (string $extractorId): Reference => new Reference($extractorId), $extractors)) ; return $extractorId; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php index 269b6e85a925d..b36745c672d5c 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php @@ -47,7 +47,7 @@ public function addConfiguration(NodeDefinition $builder) $factoryRootNode ->fixXmlConfig('custom_authenticator') ->validate() - ->ifTrue(function ($v) { return isset($v['custom_authenticators']) && empty($v['custom_authenticators']); }) + ->ifTrue(fn ($v) => isset($v['custom_authenticators']) && empty($v['custom_authenticators'])) ->then(function ($v) { unset($v['custom_authenticators']); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index d3ec4633cf5ce..8d5827232bd04 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -148,7 +148,7 @@ public function addConfiguration(NodeDefinition $node) ->scalarNode('service')->end() ->arrayNode('user_providers') ->beforeNormalization() - ->ifString()->then(function ($v) { return [$v]; }) + ->ifString()->then(fn ($v) => [$v]) ->end() ->prototype('scalar')->end() ->end() @@ -162,7 +162,7 @@ public function addConfiguration(NodeDefinition $node) ->end() ->arrayNode('token_provider') ->beforeNormalization() - ->ifString()->then(function ($v) { return ['service' => $v]; }) + ->ifString()->then(fn ($v) => ['service' => $v]) ->end() ->children() ->scalarNode('service')->info('The service ID of a custom rememberme token provider.')->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php index 0abb1ce247f5e..0c8a730d1c6d4 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php @@ -54,7 +54,7 @@ public function addConfiguration(NodeDefinition $node) ->children() ->scalarNode('password')->defaultNull()->end() ->arrayNode('roles') - ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end() ->prototype('scalar')->end() ->end() ->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php index c2a334369ca0c..670a848d0ebd4 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php @@ -59,7 +59,7 @@ public function addConfiguration(NodeDefinition $node) ->prototype('scalar')->end() ->end() ->arrayNode('default_roles') - ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end() ->requiresAtLeastOneElement() ->prototype('scalar')->end() ->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index d1724dd81b621..5e67e2bb6a612 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -501,9 +501,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ $configuredEntryPoint = $defaultEntryPoint; // authenticator manager - $authenticators = array_map(function ($id) { - return new Reference($id); - }, $firewallAuthenticationProviders); + $authenticators = array_map(fn ($id) => new Reference($id), $firewallAuthenticationProviders); $container ->setDefinition($managerId = 'security.authenticator.manager.'.$id, new ChildDefinition('security.authenticator.manager')) ->replaceArgument(0, $authenticators) @@ -1027,9 +1025,7 @@ public function getConfiguration(array $config, ContainerBuilder $container): ?C private function isValidIps(string|array $ips): bool { - $ipsList = array_reduce((array) $ips, static function (array $ips, string $ip) { - return array_merge($ips, preg_split('/\s*,\s*/', $ip)); - }, []); + $ipsList = array_reduce((array) $ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []); if (!$ipsList) { return false; @@ -1081,9 +1077,7 @@ private function getSortedFactories(): array $factories[] = array_merge($factory, [$i]); } - usort($factories, function ($a, $b) { - return $b[0] <=> $a[0] ?: $a[2] <=> $b[2]; - }); + usort($factories, fn ($a, $b) => $b[0] <=> $a[0] ?: $a[2] <=> $b[2]); $this->sortedFactories = array_column($factories, 1); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index 9b5afb0b8b20a..64e7f7adb5864 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -342,7 +342,7 @@ public function testCollectDecisionLog(string $strategy, array $decisionLog, arr $this->assertEquals($dataCollector->getAccessDecisionLog(), $expectedDecisionLog, 'Wrong value returned by getAccessDecisionLog'); $this->assertSame( - array_map(function ($classStub) { return (string) $classStub; }, $dataCollector->getVoters()), + array_map(fn ($classStub) => (string) $classStub, $dataCollector->getVoters()), $expectedVoterClasses, 'Wrong value returned by getVoters' ); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php index cecf1b04835ef..b5211da3cecd2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php @@ -191,10 +191,8 @@ private function assertListeners(array $expectedListeners, string $dispatcherId $actualListeners[] = $arguments; } - $foundListeners = array_uintersect($expectedListeners, $actualListeners, function (array $a, array $b) { - // PHP internally sorts all the arrays first, so returning proper 1 / -1 values is crucial - return $a <=> $b; - }); + // PHP internally sorts all the arrays first, so returning proper 1 / -1 values is crucial + $foundListeners = array_uintersect($expectedListeners, $actualListeners, fn (array $a, array $b) => $a <=> $b); $this->assertEquals($expectedListeners, $foundListeners); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index acf5de337fc6b..8c521c8a03414 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -93,7 +93,7 @@ public function testUserProviders() { $container = $this->getContainer('container1'); - $providers = array_values(array_filter($container->getServiceIds(), function ($key) { return str_starts_with($key, 'security.user.provider.concrete'); })); + $providers = array_values(array_filter($container->getServiceIds(), fn ($key) => str_starts_with($key, 'security.user.provider.concrete'))); $expectedProviders = [ 'security.user.provider.concrete.default', diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php index dca54cfb0d999..3060f4ef9e8c6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php @@ -46,7 +46,7 @@ public function authenticate(Request $request): Passport $userLoader = null; if ($this->selfLoadingUser) { - $userLoader = function ($username) { return new InMemoryUser($username, 'test', ['ROLE_USER']); }; + $userLoader = fn ($username) => new InMemoryUser($username, 'test', ['ROLE_USER']); } return new SelfValidatingPassport(new UserBadge($email, $userLoader)); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php index 0b466d0af7990..92703f41ec3c7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php @@ -69,14 +69,10 @@ private function createLocator(array $linkers) $locator = $this->createMock(ContainerInterface::class); $locator->expects($this->any()) ->method('has') - ->willReturnCallback(function ($firewallName) use ($linkers) { - return isset($linkers[$firewallName]); - }); + ->willReturnCallback(fn ($firewallName) => isset($linkers[$firewallName])); $locator->expects($this->any()) ->method('get') - ->willReturnCallback(function ($firewallName) use ($linkers) { - return $linkers[$firewallName]; - }); + ->willReturnCallback(fn ($firewallName) => $linkers[$firewallName]); return $locator; } diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index 0655a51e7c159..20b7427bedb9d 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -33,7 +33,7 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode = $treeBuilder->getRootNode(); $rootNode->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && \array_key_exists('exception_controller', $v); }) + ->ifTrue(fn ($v) => \is_array($v) && \array_key_exists('exception_controller', $v)) ->then(function ($v) { if (isset($v['exception_controller'])) { throw new InvalidConfigurationException('Option "exception_controller" under "twig" must be null or unset, use "error_controller" under "framework" instead.'); @@ -64,10 +64,8 @@ private function addFormThemesSection(ArrayNodeDefinition $rootNode) ->prototype('scalar')->defaultValue('form_div_layout.html.twig')->end() ->example(['@My/form.html.twig']) ->validate() - ->ifTrue(function ($v) { return !\in_array('form_div_layout.html.twig', $v); }) - ->then(function ($v) { - return array_merge(['form_div_layout.html.twig'], $v); - }) + ->ifTrue(fn ($v) => !\in_array('form_div_layout.html.twig', $v)) + ->then(fn ($v) => array_merge(['form_div_layout.html.twig'], $v)) ->end() ->end() ->end() @@ -86,7 +84,7 @@ private function addGlobalsSection(ArrayNodeDefinition $rootNode) ->prototype('array') ->normalizeKeys(false) ->beforeNormalization() - ->ifTrue(function ($v) { return \is_string($v) && str_starts_with($v, '@'); }) + ->ifTrue(fn ($v) => \is_string($v) && str_starts_with($v, '@')) ->then(function ($v) { if (str_starts_with($v, '@@')) { return substr($v, 1); @@ -106,7 +104,7 @@ private function addGlobalsSection(ArrayNodeDefinition $rootNode) return true; }) - ->then(function ($v) { return ['value' => $v]; }) + ->then(fn ($v) => ['value' => $v]) ->end() ->children() ->scalarNode('id')->end() @@ -151,7 +149,7 @@ private function addTwigOptions(ArrayNodeDefinition $rootNode) ->info('Pattern of file name used for cache warmer and linter') ->beforeNormalization() ->ifString() - ->then(function ($value) { return [$value]; }) + ->then(fn ($value) => [$value]) ->end() ->prototype('scalar')->end() ->end() diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php index 7505c1fcd36fa..09da820bbb84d 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php @@ -53,7 +53,7 @@ public function configure(Environment $environment) $environment->getExtension(CoreExtension::class)->setNumberFormat($this->decimals, $this->decimalPoint, $this->thousandsSeparator); // wrap UndefinedCallableHandler in closures for lazy-autoloading - $environment->registerUndefinedFilterCallback(function ($name) { return UndefinedCallableHandler::onUndefinedFilter($name); }); - $environment->registerUndefinedFunctionCallback(function ($name) { return UndefinedCallableHandler::onUndefinedFunction($name); }); + $environment->registerUndefinedFilterCallback(fn ($name) => UndefinedCallableHandler::onUndefinedFilter($name)); + $environment->registerUndefinedFunctionCallback(fn ($name) => UndefinedCallableHandler::onUndefinedFunction($name)); } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php b/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php index 3275ea792672b..7b0b1373b7eb8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php @@ -180,9 +180,7 @@ private function generateNonce(): string */ private function generateCspHeader(array $directives): string { - return array_reduce(array_keys($directives), function ($res, $name) use ($directives) { - return ('' !== $res ? $res.'; ' : '').sprintf('%s %s', $name, implode(' ', $directives[$name])); - }, ''); + return array_reduce(array_keys($directives), fn ($res, $name) => ('' !== $res ? $res.'; ' : '').sprintf('%s %s', $name, implode(' ', $directives[$name])), ''); } /** diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php index 86de485e031d1..2915b173e86fa 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php @@ -188,9 +188,7 @@ public function testReturns404onTokenNotFound($withCsp) $profiler ->expects($this->exactly(2)) ->method('loadProfile') - ->willReturnCallback(function ($token) { - return 'found' == $token ? new Profile($token) : null; - }) + ->willReturnCallback(fn ($token) => 'found' == $token ? new Profile($token) : null) ; $controller = $this->createController($profiler, $twig, $withCsp); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php index 9ce9a4c5c47ec..2c488b24d0d21 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php @@ -34,6 +34,6 @@ public function testIconFileContents($iconFilePath) public function provideIconFilePaths() { - return array_map(function ($filePath) { return (array) $filePath; }, glob(__DIR__.'/../../Resources/views/Icon/*.svg')); + return array_map(fn ($filePath) => (array) $filePath, glob(__DIR__.'/../../Resources/views/Icon/*.svg')); } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php index baffec7ef3cda..29164c7bc37ab 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php @@ -105,7 +105,7 @@ public function testRecursiveGet() $this->assertSame(1, $counter); $this->assertSame(1, $v); - $this->assertSame(1, $cache->get('k2', function () { return 2; })); + $this->assertSame(1, $cache->get('k2', fn () => 2)); } public function testDontSaveWhenAskedNotTo() @@ -123,9 +123,7 @@ public function testDontSaveWhenA 10000 skedNotTo() }); $this->assertSame($v1, 1); - $v2 = $cache->get('some-key', function () { - return 2; - }); + $v2 = $cache->get('some-key', fn () => 2); $this->assertSame($v2, 2, 'First value was cached and should not have been'); $v3 = $cache->get('some-key', function () { diff --git a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php index e289d039f3f36..e5cc91e12823e 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php @@ -79,7 +79,7 @@ public function testConfigureSchema() $schema = new Schema(); $adapter = new DoctrineDbalAdapter($connection); - $adapter->configureSchema($schema, $connection, fn() => true); + $adapter->configureSchema($schema, $connection, fn () => true); $this->assertTrue($schema->hasTable('cache_items')); } @@ -100,7 +100,7 @@ public function testConfigureSchemaTableExists() $schema->createTable('cache_items'); $adapter = new DoctrineDbalAdapter($connection); - $adapter->configureSchema($schema, $connection, fn() => true); + $adapter->configureSchema($schema, $connection, fn () => true); $table = $schema->getTable('cache_items'); $this->assertEmpty($table->getColumns(), 'The table was not overwritten'); } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php index 0cbe628c75759..d2aacb69e46ac 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php @@ -124,7 +124,7 @@ public function testServersSetting(string $dsn, string $host, int $port) 'port' => $port, ]; - $f = function ($s) { return ['host' => $s['host'], 'port' => $s['port']]; }; + $f = fn ($s) => ['host' => $s['host'], 'port' => $s['port']]; $this->assertSame([$expect], array_map($f, $client1->getServerList())); $this->assertSame([$expect], array_map($f, $client2->getServerList())); $this->assertSame([$expect], array_map($f, $client3->getServerList())); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php index 9752faab3aba9..5960618ec9b13 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php @@ -48,9 +48,7 @@ public function testCleanupExpiredItems() { $pdo = new \PDO('sqlite:'.self::$dbFile); - $getCacheItemCount = function () use ($pdo) { - return (int) $pdo->query('SELECT COUNT(*) FROM cache_items')->fetch(\PDO::FETCH_COLUMN); - }; + $getCacheItemCount = fn () => (int) $pdo->query('SELECT COUNT(*) FROM cache_items')->fetch(\PDO::FETCH_COLUMN); $this->assertSame(0, $getCacheItemCount()); diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/DefaultMarshallerTest.php b/src/Symfony/Component/Cache/Tests/Marshaller/DefaultMarshallerTest.php index 4bc238ed341bd..b53579fe97314 100644 --- a/src/Symfony/Component/Cache/Tests/Marshaller/DefaultMarshallerTest.php +++ b/src/Symfony/Component/Cache/Tests/Marshaller/DefaultMarshallerTest.php @@ -82,7 +82,7 @@ public function testNativeUnserializeInvalid() $this->expectException(\DomainException::class); $this->expectExceptionMessage('unserialize(): Error at offset 0 of 3 bytes'); $marshaller = new DefaultMarshaller(); - set_error_handler(function () { return false; }); + set_error_handler(fn () => false); try { @$marshaller->unmarshall(':::'); } finally { @@ -102,7 +102,7 @@ public function testIgbinaryUnserializeInvalid() $this->expectException(\DomainException::class); $this->expectExceptionMessage('igbinary_unserialize_zval: unknown type \'61\', position 5'); $marshaller = new DefaultMarshaller(); - set_error_handler(function () { return false; }); + set_error_handler(fn () => false); try { @$marshaller->unmarshall(rawurldecode('%00%00%00%02abc')); } finally { diff --git a/src/Symfony/Component/Cache/Tests/Messenger/EarlyExpirationDispatcherTest.php b/src/Symfony/Component/Cache/Tests/Messenger/EarlyExpirationDispatcherTest.php index a471cc18d1045..cdaef155b5c28 100644 --- a/src/Symfony/Component/Cache/Tests/Messenger/EarlyExpirationDispatcherTest.php +++ b/src/Symfony/Component/Cache/Tests/Messenger/EarlyExpirationDispatcherTest.php @@ -67,7 +67,7 @@ public function __invoke(CacheItem $item) } }); - $this->assertSame(345, $pool->get('foo', function () { return 345; })); + $this->assertSame(345, $pool->get('foo', fn () => 345)); $this->assertTrue($saveResult); $expected = [ diff --git a/src/Symfony/Component/Cache/Traits/ContractsTrait.php b/src/Symfony/Component/Cache/Traits/ContractsTrait.php index a8e347a32623e..60b7d2bf98b4e 100644 --- a/src/Symfony/Component/Cache/Traits/ContractsTrait.php +++ b/src/Symfony/Component/Cache/Traits/ContractsTrait.php @@ -54,9 +54,7 @@ public function setCallbackWrapper(?callable $callbackWrapper): callable } $previousWrapper = $this->callbackWrapper; - $this->callbackWrapper = $callbackWrapper ?? static function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) { - return $callback($item, $save); - }; + $this->callbackWrapper = $callbackWrapper ?? static fn (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) => $callback($item, $save); return $previousWrapper; } diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index e06029ee4f677..89f8f5e5d0f58 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -553,7 +553,7 @@ private function pipeline(\Closure $generator, object $redis = null): \Generator if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) { $e = new \RedisException($redis->getLastError()); - $results = array_map(function ($v) use ($e) { return false === $v ? $e : $v; }, (array) $results); + $results = array_map(fn ($v) => false === $v ? $e : $v, (array) $results); } if (\is_bool($results)) { diff --git a/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php b/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php index 8dc7f4cb5f885..44ad6ca894f75 100644 --- a/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php +++ b/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php @@ -426,9 +426,7 @@ private function getComment(BaseNode $node): string } if ($node instanceof EnumNode) { - $comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_map(function ($a) { - return var_export($a, true); - }, $node->getValues())))."\n"; + $comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_map(fn ($a) => var_export($a, true), $node->getValues())))."\n"; } else { $parameterTypes = $this->getParameterTypes($node); $comment .= ' * @param ParamConfigurator|'.implode('|', $parameterTypes).' $value'."\n"; diff --git a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php index 8dee2d394aeb9..2eeb4a1a4f0ad 100644 --- a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php +++ b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php @@ -53,9 +53,7 @@ private function writeNode(NodeInterface $node, int $depth = 0, bool $root = fal // xml remapping if ($node->getParent()) { - $remapping = array_filter($node->getParent()->getXmlRemappings(), function (array $mapping) use ($rootName) { - return $rootName === $mapping[1]; - }); + $remapping = array_filter($node->getParent()->getXmlRemappings(), fn (array $mapping) => $rootName === $mapping[1]); if (\count($remapping)) { [$singular] = current($remapping); diff --git a/src/Symfony/Component/Config/Resource/GlobResource.php b/src/Symfony/Component/Config/Resource/GlobResource.php index 7acfcd663b410..fde1dcb87d274 100644 --- a/src/Symfony/Component/Config/Resource/GlobResource.php +++ b/src/Symfony/Component/Config/Resource/GlobResource.php @@ -144,9 +144,7 @@ public function getIterator(): \Traversable $files = iterator_to_array(new \RecursiveIteratorIterator( new \RecursiveCallbackFilterIterator( new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), - function (\SplFileInfo $file, $path) { - return !isset($this->excludedPrefixes[str_replace('\\', '/', $path)]) && '.' !== $file->getBasename()[0]; - } + fn (\SplFileInfo $file, $path) => !isset($this->excludedPrefixes[str_replace('\\', '/', $path)]) && '.' !== $file->getBasename()[0] ), \RecursiveIteratorIterator::LEAVES_ONLY )); diff --git a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php index 279ff203c2ac8..c8d685dbef28c 100644 --- a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php +++ b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php @@ -154,7 +154,7 @@ private function generateSignature(\ReflectionClass $class): iterable } } - $defined = \Closure::bind(static function ($c) { return \defined($c); }, null, $class->name); + $defined = \Closure::bind(static fn ($c) => \defined($c), null, $class->name); foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { foreach ($m->getAttributes() as $a) { diff --git a/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php b/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php index a57a0c12c0890..9e77618170d9e 100644 --- a/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php @@ -239,9 +239,7 @@ public function testUnsetChild() ->children() ->scalarNode('value') ->beforeNormalization() - ->ifTrue(function ($value) { - return empty($value); - }) + ->ifTrue(fn ($value) => empty($value)) ->thenUnset() ->end() ->end() diff --git a/src/Symfony/Component/Config/Tests/Definition/Builder/ExprBuilderTest.php b/src/Symfony/Component/Config/Tests/Definition/Builder/ExprBuilderTest.php index e96c57c409f8f..19a84e49b10fe 100644 --- a/src/Symfony/Component/Config/Tests/Definition/Builder/ExprBuilderTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/Builder/ExprBuilderTest.php @@ -37,13 +37,13 @@ public function testIfTrueExpression() $this->assertFinalizedValueIs('new_value', $test, ['key' => true]); $test = $this->getTestBuilder() - ->ifTrue(function () { return true; }) + ->ifTrue(fn () => true) ->then($this->returnClosure('new_value')) ->end(); $this->assertFinalizedValueIs('new_value', $test); $test = $this->getTestBuilder() - ->ifTrue(function () { return false; }) + ->ifTrue(fn () => false) ->then($this->returnClosure('new_value')) ->end(); $this->assertFinalizedValueIs('value', $test); @@ -241,9 +241,7 @@ protected function finalizeTestBuilder(NodeDefinition $nodeDefinition, array $co */ protected function returnClosure($val): \Closure { - return function () use ($val) { - return $val; - }; + return fn () => $val; } /** diff --git a/src/Symfony/Component/Config/Tests/Definition/NormalizationTest.php b/src/Symfony/Component/Config/Tests/Definition/NormalizationTest.php index 0b497707e4604..07f949ae042ac 100644 --- a/src/Symfony/Component/Config/Tests/Definition/NormalizationTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/NormalizationTest.php @@ -31,7 +31,7 @@ public function testNormalizeEncoders($denormalized) ->node('encoders', 'array') ->useAttributeAsKey('class') ->prototype('array') - ->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => ['algorithm' => $v])->end() ->children() ->node('algorithm', 'scalar')->end() ->end() @@ -88,9 +88,7 @@ public function getEncoderTests(): array ], ]; - return array_map(function ($v) { - return [$v]; - }, $configs); + return array_map(fn ($v) => [$v], $configs); } /** @@ -136,7 +134,7 @@ public function getAnonymousKeysTests(): array ], ]; - return array_map(function ($v) { return [$v]; }, $configs); + return array_map(fn ($v) => [$v], $configs); } /** @@ -167,7 +165,7 @@ public function getNumericKeysTests(): array ], ]; - return array_map(function ($v) { return [$v]; }, $configs); + return array_map(fn ($v) => [$v], $configs); } public function testNonAssociativeArrayThrowsExceptionIfAttributeNotSet() diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index f900ec12a9574..40c7035af4875 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -687,9 +687,7 @@ public function find(string $name) if ($alternatives = $this->findAlternatives($name, $allCommands)) { // remove hidden commands - $alternatives = array_filter($alternatives, function ($name) { - return !$this->get($name)->isHidden(); - }); + $alternatives = array_filter($alternatives, fn ($name) => !$this->get($name)->isHidden()); if (1 == \count($alternatives)) { $message .= "\n\nDid you mean this?\n "; @@ -840,9 +838,7 @@ protected function doRenderThrowable(\Throwable $e, OutputInterface $output): vo } if (str_contains($message, "@anonymous\0")) { - $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { - return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; - }, $message); + $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message); } $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX; @@ -1163,7 +1159,7 @@ private function findAlternatives(string $name, iterable $collection): array } } - $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold); ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); return array_keys($alternatives); diff --git a/src/Symfony/Component/Console/Command/CompleteCommand.php b/src/Symfony/Component/Console/Command/CompleteCommand.php index ba3042e786278..b251f88716b14 100644 --- a/src/Symfony/Component/Console/Command/CompleteCommand.php +++ b/src/Symfony/Component/Console/Command/CompleteCommand.php @@ -155,7 +155,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->log('Suggestions:'); if ($options = $suggestions->getOptionSuggestions()) { - $this->log(' --'.implode(' --', array_map(function ($o) { return $o->getName(); }, $options))); + $this->log(' --'.implode(' --', array_map(fn ($o) => $o->getName(), $options))); } elseif ($values = $suggestions->getValueSuggestions()) { $this->log(' '.implode(' ', $values)); } else { diff --git a/src/Symfony/Component/Console/Command/DumpCompletionCommand.php b/src/Symfony/Component/Console/Command/DumpCompletionCommand.php index 1ad1c0e7b6bf7..54146d2305257 100644 --- a/src/Symfony/Component/Console/Command/DumpCompletionCommand.php +++ b/src/Symfony/Component/Console/Command/DumpCompletionCommand.php @@ -141,8 +141,6 @@ private function tailDebugLog(string $commandName, OutputInterface $output): voi */ private function getSupportedShells(): array { - return $this->supportedShells ??= array_map(function ($f) { - return pathinfo($f, \PATHINFO_EXTENSION); - }, glob(__DIR__.'/../Resources/completion.*')); + return $this->supportedShells ??= array_map(fn ($f) => pathinfo($f, \PATHINFO_EXTENSION), glob(__DIR__.'/../Resources/completion.*')); } } diff --git a/src/Symfony/Component/Console/Command/HelpCommand.php b/src/Symfony/Component/Console/Command/HelpCommand.php index d4134e170b454..29c7e61c13355 100644 --- a/src/Symfony/Component/Console/Command/HelpCommand.php +++ b/src/Symfony/Component/Console/Command/HelpCommand.php @@ -34,12 +34,8 @@ protected function configure() $this ->setName('help') ->setDefinition([ - new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', function () { - return array_keys((new ApplicationDescription($this->getApplication()))->getCommands()); - }), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () { - return (new DescriptorHelper())->getFormats(); - }), + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', fn () => array_keys((new ApplicationDescription($this->getApplication()))->getCommands())), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), ]) ->setDescription('Display help for a command') diff --git a/src/Symfony/Component/Console/Command/ListCommand.php b/src/Symfony/Component/Console/Command/ListCommand.php index cab88b4392291..91edfe695d2b6 100644 --- a/src/Symfony/Component/Console/Command/ListCommand.php +++ b/src/Symfony/Component/Console/Command/ListCommand.php @@ -30,13 +30,9 @@ protected function configure() $this ->setName('list') ->setDefinition([ - new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, function () { - return array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces()); - }), + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () { - return (new DescriptorHelper())->getFormats(); - }), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()), new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'), ]) ->setDescription('List commands') diff --git a/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php index fbd9c534616e0..16f78de1333a9 100644 --- a/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php @@ -110,9 +110,7 @@ protected function describeCommand(Command $command, array $options = []) .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" .($command->getDescription() ? $command->getDescription()."\n\n" : '') .'### Usage'."\n\n" - .array_reduce($command->getAliases(), function ($carry, $usage) { - return $carry.'* `'.$usage.'`'."\n"; - }) + .array_reduce($command->getAliases(), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n") ); return; @@ -125,9 +123,7 @@ protected function describeCommand(Command $command, array $options = []) .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" .($command->getDescription() ? $command->getDescription()."\n\n" : '') .'### Usage'."\n\n" - .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) { - return $carry.'* `'.$usage.'`'."\n"; - }) + .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n") ); if ($help = $command->getProcessedHelp()) { @@ -157,9 +153,7 @@ protected function describeApplication(Application $application, array $options } $this->write("\n\n"); - $this->write(implode("\n", array_map(function ($commandName) use ($description) { - return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())); - }, $namespace['commands']))); + $this->write(implode("\n", array_map(fn ($commandName) => sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands']))); } foreach ($description->getCommands() as $command) { diff --git a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php index 48a0b42af9b10..31f5b4063bd89 100644 --- a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php @@ -193,9 +193,7 @@ protected function describeApplication(Application $application, array $options } // calculate max. width based on available commands per namespace - $width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) { - return array_intersect($namespace['commands'], array_keys($commands)); - }, array_values($namespaces))))); + $width = $this->getColumnWidth(array_merge(...array_values(array_map(fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces))))); if ($describedNamespace) { $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); @@ -204,9 +202,7 @@ protected function describeApplication(Application $application, array $options } foreach ($namespaces as $namespace) { - $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) { - return isset($commands[$name]); - }); + $namespace['commands'] = array_filter($namespace['commands'], fn ($name) => isset($commands[$name])); if (!$namespace['commands']) { continue; diff --git a/src/Symfony/Component/Console/Helper/Dumper.php b/src/Symfony/Component/Console/Helper/Dumper.php index ac7571ceafa7d..8c6a94d51fa5f 100644 --- a/src/Symfony/Component/Console/Helper/Dumper.php +++ b/src/Symfony/Component/Console/Helper/Dumper.php @@ -40,14 +40,12 @@ public function __construct(OutputInterface $output, CliDumper $dumper = null, C return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true)); }; } else { - $this->handler = function ($var): string { - return match (true) { - null === $var => 'null', - true === $var => 'true', - false === $var => 'false', - \is_string($var) => '"'.$var.'"', - default => rtrim(print_r($var, true)), - }; + $this->handler = fn ($var): string => match (true) { + null === $var => 'null', + true === $var => 'true', + false === $var => 'false', + \is_string($var) => '"'.$var.'"', + default => rtrim(print_r($var, true)), }; } } diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index 8dad297d5e855..e5ed5c29b909b 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -539,9 +539,7 @@ private static function initPlaceholderFormatters(): array return $display; }, - 'elapsed' => function (self $bar) { - return Helper::formatTime(time() - $bar->getStartTime()); - }, + 'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime()), 'remaining' => function (self $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); @@ -556,18 +554,10 @@ private static function initPlaceholderFormatters(): array return Helper::formatTime($bar->getEstimated()); }, - 'memory' => function (self $bar) { - return Helper::formatMemory(memory_get_usage(true)); - }, - 'current' => function (self $bar) { - return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT); - }, - 'max' => function (self $bar) { - return $bar->getMaxSteps(); - }, - 'percent' => function (self $bar) { - return floor($bar->getProgressPercent() * 100); - }, + 'memory' => fn (self $bar) => Helper::formatMemory(memory_get_usage(true)), + 'current' => fn (self $bar) => str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT), + 'max' => fn (self $bar) => $bar->getMaxSteps(), + 'percent' => fn (self $bar) => floor($bar->getProgressPercent() * 100), ]; } @@ -611,9 +601,7 @@ private function buildLine(): string $line = preg_replace_callback($regex, $callback, $this->format); // gets string length for each sub line with multiline format - $linesLength = array_map(function ($subLine) { - return Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))); - }, explode("\n", $line)); + $linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))), explode("\n", $line)); $linesWidth = max($linesLength); diff --git a/src/Symfony/Component/Console/Helper/ProgressIndicator.php b/src/Symfony/Component/Console/Helper/ProgressIndicator.php index 172036465c5b8..5ca6309f8ff36 100644 --- a/src/Symfony/Component/Console/Helper/ProgressIndicator.php +++ b/src/Symfony/Component/Console/Helper/ProgressIndicator.php @@ -218,18 +218,10 @@ private function getCurrentTimeInMilliseconds(): float private static function initPlaceholderFormatters(): array { return [ - 'indicator' => function (self $indicator) { - return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)]; - }, - 'message' => function (self $indicator) { - return $indicator->message; - }, - 'elapsed' => function (self $indicator) { - return Helper::formatTime(time() - $indicator->startTime); - }, - 'memory' => function () { - return Helper::formatMemory(memory_get_usage(true)); - }, + 'indicator' => fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)], + 'message' => fn (self $indicator) => $indicator->message, + 'elapsed' => fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime), + 'memory' => fn () => Helper::formatMemory(memory_get_usage(true)), ]; } } diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index c345b4af7747f..7297a236422eb 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -68,9 +68,7 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu return $this->doAsk($output, $question); } - $interviewer = function () use ($output, $question) { - return $this->doAsk($output, $question); - }; + $interviewer = fn () => $this->doAsk($output, $question); return $this->validateAttempts($interviewer, $output, $question); } catch (MissingInputException $exception) { @@ -314,9 +312,7 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu $matches = array_filter( $autocomplete($ret), - function ($match) use ($ret) { - return '' === $ret || str_starts_with($match, $ret); - } + fn ($match) => '' === $ret || str_starts_with($match, $ret) ); $numMatches = \count($matches); $ofs = -1; diff --git a/src/Symfony/Component/Console/Helper/TableCellStyle.php b/src/Symfony/Component/Console/Helper/TableCellStyle.php index 65ae9e728109f..9419dcb402e05 100644 --- a/src/Symfony/Component/Console/Helper/TableCellStyle.php +++ b/src/Symfony/Component/Console/Helper/TableCellStyle.php @@ -67,9 +67,7 @@ public function getTagOptions(): array { return array_filter( $this->getOptions(), - function ($key) { - return \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]); - }, + fn ($key) => \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]), \ARRAY_FILTER_USE_KEY ); } diff --git a/src/Symfony/Component/Console/Input/Input.php b/src/Symfony/Component/Console/Input/Input.php index 7b90713c865f5..4cfb0bb8d0d83 100644 --- a/src/Symfony/Component/Console/Input/Input.php +++ b/src/Symfony/Component/Console/Input/Input.php @@ -62,9 +62,7 @@ public function validate() $definition = $this->definition; $givenArguments = $this->arguments; - $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { - return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); - }); + $missingArguments = array_filter(array_keys($definition->getArguments()), fn ($argument) => !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired()); if (\count($missingArguments) > 0) { throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); diff --git a/src/Symfony/Component/Console/Question/Question.php b/src/Symfony/Component/Console/Question/Question.php index b06db9459bccf..58b09c3543456 100644 --- a/src/Symfony/Component/Console/Question/Question.php +++ b/src/Symfony/Component/Console/Question/Question.php @@ -146,9 +146,7 @@ public function setAutocompleterValues(?iterable $values): static if (\is_array($values)) { $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); - $callback = static function () use ($values) { - return $values; - }; + $callback = static fn () => $values; } elseif ($values instanceof \Traversable) { $valueCache = null; $callback = static function () use ($values, &$valueCache) { diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php index 4dde785e86420..4225823b8d27a 100644 --- a/src/Symfony/Component/Console/Style/SymfonyStyle.php +++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php @@ -92,9 +92,7 @@ public function section(string $message) public function listing(array $elements) { $this->autoPrependText(); - $elements = array_map(function ($element) { - return sprintf(' * %s', $element); - }, $elements); + $elements = array_map(fn ($element) => sprintf(' * %s', $element), $elements); $this->writeln($elements); $this->newLine(); diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index f6eff84dacec7..78f6c025bef37 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -164,7 +164,7 @@ public function testAllWithCommandLoader() $this->assertCount(1, $commands, '->all() takes a namespace as its first argument'); $application->setCommandLoader(new FactoryCommandLoader([ - 'foo:bar1' => function () { return new \Foo1Command(); }, + 'foo:bar1' => fn () => new \Foo1Command(), ])); $commands = $application->all('foo'); $this->assertCount(2, $commands, '->all() takes a namespace as its first argument'); @@ -255,7 +255,7 @@ public function testHasGetWithCommandLoader() $this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias'); $application->setCommandLoader(new FactoryCommandLoader([ - 'foo:bar1' => function () { return new \Foo1Command(); }, + 'foo:bar1' => fn () => new \Foo1Command(), ])); $this->assertTrue($application->has('afoobar'), '->has() returns true if an instance is registered for an alias even with command loader'); @@ -405,7 +405,7 @@ public function testFindWithCommandLoader() { $application = new Application(); $application->setCommandLoader(new FactoryCommandLoader([ - 'foo:bar' => $f = function () { return new \FooCommand(); }, + 'foo:bar' => $f = fn () => new \FooCommand(), ])); $this->assertInstanceOf(\FooCommand::class, $application->find('foo:bar'), '->find() returns a command if its name exists'); @@ -654,7 +654,7 @@ public function testFindAlternativeCommandsWithAnAlias() $application = new Application(); $application->setCommandLoader(new FactoryCommandLoader([ - 'foo3' => static function () use ($fooCommand) { return $fooCommand; }, + 'foo3' => static fn () => $fooCommand, ])); $application->add($fooCommand); @@ -1769,21 +1769,21 @@ public function testGetDisabledLazyCommand() { $this->expectException(CommandNotFoundException::class); $application = new Application(); - $application->setCommandLoader(new FactoryCommandLoader(['disabled' => function () { return new DisabledCommand(); }])); + $application->setCommandLoader(new FactoryCommandLoader(['disabled' => fn () => new DisabledCommand()])); $application->get('disabled'); } public function testHasReturnsFalseForDisabledLazyCommand() { $application = new Application(); - $application->setCommandLoader(new FactoryCommandLoader(['disabled' => function () { return new DisabledCommand(); }])); + $application->setCommandLoader(new FactoryCommandLoader(['disabled' => fn () => new DisabledCommand()])); $this->assertFalse($application->has('disabled')); } public function testAllExcludesDisabledLazyCommand() { $application = new Application(); - $application->setCommandLoader(new FactoryCommandLoader(['disabled' => function () { return new DisabledCommand(); }])); + $application->setCommandLoader(new FactoryCommandLoader(['disabled' => fn () => new DisabledCommand()])); $this->assertArrayNotHasKey('disabled', $application->all()); } @@ -1891,7 +1891,7 @@ public function testCommandNameMismatchWithCommandLoaderKeyThrows() $app = new Application(); $loader = new FactoryCommandLoader([ - 'test' => static function () { return new Command('test-command'); }, + 'test' => static fn () => new Command('test-command'), ]); $app->setCommandLoader($loader); @@ -2095,7 +2095,7 @@ private function createSignalableApplication(Command $command, ?EventDispatcherI if ($dispatcher) { $application->setDispatcher($dispatcher); } - $application->add(new LazyCommand('signal', [], '', false, function () use ($command) { return $command; }, true)); + $application->add(new LazyCommand('signal', [], '', false, fn () => $command, true)); return $application; } diff --git a/src/Symfony/Component/Console/Tests/CommandLoader/ContainerCommandLoaderTest.php b/src/Symfony/Component/Console/Tests/CommandLoader/ContainerCommandLoaderTest.php index e7f138933ae7a..f2049ef5cc8d7 100644 --- a/src/Symfony/Component/Console/Tests/CommandLoader/ContainerCommandLoaderTest.php +++ b/src/Symfony/Component/Console/Tests/CommandLoader/ContainerCommandLoaderTest.php @@ -22,8 +22,8 @@ class ContainerCommandLoaderTest extends TestCase public function testHas() { $loader = new ContainerCommandLoader(new ServiceLocator([ - 'foo-service' => function () { return new Command('foo'); }, - 'bar-service' => function () { return new Command('bar'); }, + 'foo-service' => fn () => new Command('foo'), + 'bar-service' => fn () => new Command('bar'), ]), ['foo' => 'foo-service', 'bar' => 'bar-service']); $this->assertTrue($loader->has('foo')); @@ -34,8 +34,8 @@ public function testHas() public function testGet() { $loader = new ContainerCommandLoader(new ServiceLocator([ - 'foo-service' => function () { return new Command('foo'); }, - 'bar-service' => function () { return new Command('bar'); }, + 'foo-service' => fn () => new Command('foo'), + 'bar-service' => fn () => new Command('bar'), ]), ['foo' => 'foo-service', 'bar' => 'bar-service']); $this->assertInstanceOf(Command::class, $loader->get('foo')); @@ -51,8 +51,8 @@ public function testGetUnknownCommandThrows() public function testGetCommandNames() { $loader = new ContainerCommandLoader(new ServiceLocator([ - 'foo-service' => function () { return new Command('foo'); }, - 'bar-service' => function () { return new Command('bar'); }, + 'foo-service' => fn () => new Command('foo'), + 'bar-service' => fn () => new Command('bar'), ]), ['foo' => 'foo-service', 'bar' => 'bar-service']); $this->assertSame(['foo', 'bar'], $loader->getNames()); diff --git a/src/Symfony/Component/Console/Tests/CommandLoader/FactoryCommandLoaderTest.php b/src/Symfony/Component/Console/Tests/CommandLoader/FactoryCommandLoaderTest.php index aebb429e652f7..148806413e78a 100644 --- a/src/Symfony/Component/Console/Tests/CommandLoader/FactoryCommandLoaderTest.php +++ b/src/Symfony/Component/Console/Tests/CommandLoader/FactoryCommandLoaderTest.php @@ -21,8 +21,8 @@ class FactoryCommandLoaderTest extends TestCase public function testHas() { $loader = new FactoryCommandLoader([ - 'foo' => function () { return new Command('foo'); }, - 'bar' => function () { return new Command('bar'); }, + 'foo' => fn () => new Command('foo'), + 'bar' => fn () => new Command('bar'), ]); $this->assertTrue($loader->has('foo')); @@ -33,8 +33,8 @@ public function testHas() public function testGet() { $loader = new FactoryCommandLoader([ - 'foo' => function () { return new Command('foo'); }, - 'bar' => function () { return new Command('bar'); }, + 'foo' => fn () => new Command('foo'), + 'bar' => fn () => new Command('bar'), ]); $this->assertInstanceOf(Command::class, $loader->get('foo')); @@ -50,8 +50,8 @@ public function testGetUnknownCommandThrows() public function testGetCommandNames() { $loader = new FactoryCommandLoader([ - 'foo' => function () { return new Command('foo'); }, - 'bar' => function () { return new Command('bar'); }, + 'foo' => fn () => new Command('foo'), + 'bar' => fn () => new Command('bar'), ]); $this->assertSame(['foo', 'bar'], $loader->getNames()); diff --git a/src/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php index e2880f22f4317..f9cd3b9111454 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php @@ -99,7 +99,7 @@ public function provideCommandsAndOutput() $args = new Process(['php', '-r', 'echo 42;']); $args = $args->getCommandLine(); $successOutputProcessDebug = str_replace("'php' '-r' 'echo 42;'", $args, $successOutputProcessDebug); - $fromShellCommandline = method_exists(Process::class, 'fromShellCommandline') ? [Process::class, 'fromShellCommandline'] : function ($cmd) { return new Process($cmd); }; + $fromShellCommandline = method_exists(Process::class, 'fromShellCommandline') ? [Process::class, 'fromShellCommandline'] : fn ($cmd) => new Process($cmd); return [ ['', 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERBOSE, null], diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index 3a44ba6626259..73b3a7c97e1b5 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -842,9 +842,7 @@ public function testWithSmallScreen() public function testAddingPlaceholderFormatter() { - ProgressBar::setPlaceholderFormatterDefinition('remaining_steps', function (ProgressBar $bar) { - return $bar->getMaxSteps() - $bar->getProgress(); - }); + ProgressBar::setPlaceholderFormatterDefinition('remaining_steps', fn (ProgressBar $bar) => $bar->getMaxSteps() - $bar->getProgress()); $bar = new ProgressBar($output = $this->getOutputStream(), 3, 0); $bar->setFormat(' %remaining_steps% [%bar%]'); @@ -865,9 +863,7 @@ public function testAddingInstancePlaceholderFormatter() { $bar = new ProgressBar($output = $this->getOutputStream(), 3, 0); $bar->setFormat(' %countdown% [%bar%]'); - $bar->setPlaceholderFormatter('countdown', $function = function (ProgressBar $bar) { - return $bar->getMaxSteps() - $bar->getProgress(); - }); + $bar->setPlaceholderFormatter('countdown', $function = fn (ProgressBar $bar) => $bar->getMaxSteps() - $bar->getProgress()); $this->assertSame($function, $bar->getPlaceholderFormatter('countdown')); diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 1eb3eeed7de5b..59f8c7598f963 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -286,9 +286,7 @@ public function testAskWithAutocompleteCallback() $suggestionBase = $inputWords ? implode(' ', $inputWords).' ' : ''; return array_map( - function ($word) use ($suggestionBase) { - return $suggestionBase.$word.' '; - }, + fn ($word) => $suggestionBase.$word.' ', $knownWords ); }; diff --git a/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php index 72c3f879f2c69..d32fbc2a58d93 100644 --- a/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php @@ -101,7 +101,7 @@ public function testAskReturnsNullIfValidatorAllowsIt() { $questionHelper = new SymfonyQuestionHelper(); $question = new Question('What is your favorite superhero?'); - $question->setValidator(function ($value) { return $value; }); + $question->setValidator(fn ($value) => $value); $input = $this->createStreamableInputInterfaceMock($this->getInputStream("\n")); $this->assertNull($questionHelper->ask($input, $this->createOutputInterface(), $question)); } diff --git a/src/Symfony/Component/Console/Tests/Question/QuestionTest.php b/src/Symfony/Component/Console/Tests/Question/QuestionTest.php index 55e9d58d4a2c7..8235e3995fdad 100644 --- a/src/Symfony/Component/Console/Tests/Question/QuestionTest.php +++ b/src/Symfony/Component/Console/Tests/Question/QuestionTest.php @@ -62,7 +62,7 @@ public function testIsHiddenDefault() public function testSetHiddenWithAutocompleterCallback() { $this->question->setAutocompleterCallback( - function (string $input): array { return []; } + fn (string $input): array => [] ); $this->expectException(\LogicException::class); @@ -76,7 +76,7 @@ function (string $input): array { return []; } public function testSetHiddenWithNoAutocompleterCallback() { $this->question->setAutocompleterCallback( - function (string $input): array { return []; } + fn (string $input): array => [] ); $this->question->setAutocompleterCallback(null); @@ -187,7 +187,7 @@ public function testGetAutocompleterValuesDefault() public function testGetSetAutocompleterCallback() { - $callback = function (string $input): array { return []; }; + $callback = fn (string $input): array => []; $this->question->setAutocompleterCallback($callback); self::assertSame($callback, $this->question->getAutocompleterCallback()); @@ -208,7 +208,7 @@ public function testSetAutocompleterCallbackWhenHidden() ); $this->question->setAutocompleterCallback( - function (string $input): array { return []; } + fn (string $input): array => [] ); } @@ -220,7 +220,7 @@ public function testSetAutocompleterCallbackWhenNotHidden() $exception = null; try { $this->question->setAutocompleterCallback( - function (string $input): array { return []; } + fn (string $input): array => [] ); } catch (\Exception $exception) { // Do nothing @@ -232,7 +232,7 @@ function (string $input): array { return []; } public function providerGetSetValidator() { return [ - [function ($input) { return $input; }], + [fn ($input) => $input], [null], ]; } @@ -288,7 +288,7 @@ public function testGetMaxAttemptsDefault() public function testGetSetNormalizer() { - $normalizer = function ($input) { return $input; }; + $normalizer = fn ($input) => $input; $this->question->setNormalizer($normalizer); self::assertSame($normalizer, $this->question->getNormalizer()); } diff --git a/src/Symfony/Component/CssSelector/Node/FunctionNode.php b/src/Symfony/Component/CssSelector/Node/FunctionNode.php index 8428bacc9c2e9..938a82b1a70a5 100644 --- a/src/Symfony/Component/CssSelector/Node/FunctionNode.php +++ b/src/Symfony/Component/CssSelector/Node/FunctionNode.php @@ -64,9 +64,7 @@ public function getSpecificity(): Specificity public function __toString(): string { - $arguments = implode(', ', array_map(function (Token $token) { - return "'".$token->getValue()."'"; - }, $this->arguments)); + $arguments = implode(', ', array_map(fn (Token $token) => "'".$token->getValue()."'", $this->arguments)); return sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : ''); } diff --git a/src/Symfony/Component/CssSelector/Parser/Parser.php b/src/Symfony/Component/CssSelector/Parser/Parser.php index d611efec8d31f..101df57f3866f 100644 --- a/src/Symfony/Component/CssSelector/Parser/Parser.php +++ b/src/Symfony/Component/CssSelector/Parser/Parser.php @@ -57,9 +57,7 @@ public static function parseSeries(array $tokens): array } } - $joined = trim(implode('', array_map(function (Token $token) { - return $token->getValue(); - }, $tokens))); + $joined = trim(implode('', array_map(fn (Token $token) => $token->getValue(), $tokens))); $int = function ($string) { if (!is_numeric($string)) { diff --git a/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php b/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php index 48a67f5ab6678..919357b5d9492 100644 --- a/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php +++ b/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php @@ -25,9 +25,7 @@ public function testParser($source, $representation) { $parser = new Parser(); - $this->assertEquals($representation, array_map(function (SelectorNode $node) { - return (string) $node->getTree(); - }, $parser->parse($source))); + $this->assertEquals($representation, array_map(fn (SelectorNode $node) => (string) $node->getTree(), $parser->parse($source))); } /** @dataProvider getParserExceptionTestData */ diff --git a/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php b/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php index fe927c91b887d..e58293489d419 100644 --- a/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php +++ b/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php @@ -43,6 +43,6 @@ public function get(string $id): mixed public function getProvidedServices(): array { - return $this->serviceTypes ??= array_map(function () { return '?'; }, $this->serviceMap); + return $this->serviceTypes ??= array_map(fn () => '?', $this->serviceMap); } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 66a175d76c267..6aa1367d25995 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -518,9 +518,7 @@ private function createTypeNotFoundMessageCallback(TypedReference $reference, st } $currentId = $this->currentId; - return (function () use ($reference, $label, $currentId) { - return $this->createTypeNotFoundMessage($reference, $label, $currentId); - })->bindTo($this->typesClone); + return (fn () => $this->createTypeNotFoundMessage($reference, $label, $currentId))->bindTo($this->typesClone); } private function createTypeNotFoundMessage(TypedReference $reference, string $label, string $currentId): string diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php index 309bf63118d4e..9d433131b30a8 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php @@ -92,7 +92,7 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam } } - uasort($services, static function ($a, $b) { return $b[0] <=> $a[0] ?: $a[1] <=> $b[1]; }); + uasort($services, static fn ($a, $b) => $b[0] <=> $a[0] ?: $a[1] <=> $b[1]); $refs = []; foreach ($services as [, , $index, $serviceId, $class]) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php index c8217154741c9..2e404244ffa71 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php @@ -154,7 +154,7 @@ private function processDefinition(ContainerBuilder $container, string $id, Defi private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container): array { // make each value an array of ChildDefinition - $conditionals = array_map(function ($childDef) { return [$childDef]; }, $autoconfiguredInstanceof); + $conditionals = array_map(fn ($childDef) => [$childDef], $autoconfiguredInstanceof); foreach ($instanceofConditionals as $interface => $instanceofDef) { // make sure the interface/class exists (but don't validate automaticInstanceofConditionals) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index ae7ca22ee5035..5dcc490caa6c4 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -1020,9 +1020,7 @@ private function createService(Definition $definition, array &$inlineServices, b } elseif (!\is_string($factory)) { throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory.', $id)); } elseif (str_starts_with($factory, '@=')) { - $factory = function (ServiceLocator $arguments) use ($factory) { - return $this->getExpressionLanguage()->evaluate(substr($factory, 2), ['container' => $this, 'args' => $arguments]); - }; + $factory = fn (ServiceLocator $arguments) => $this->getExpressionLanguage()->evaluate(substr($factory, 2), ['container' => $this, 'args' => $arguments]); $arguments = [new ServiceLocatorArgument($arguments)]; } } @@ -1127,9 +1125,7 @@ private function doResolveServices(mixed $value, array &$inlineServices = [], bo } } elseif ($value instanceof ServiceClosureArgument) { $reference = $value->getValues()[0]; - $value = function () use ($reference) { - return $this->resolveServices($reference); - }; + $value = fn () => $this->resolveServices($reference); } elseif ($value instanceof IteratorArgument) { $value = new RewindableGenerator(function () use ($value, &$inlineServices) { foreach ($value->getValues() as $k => $v) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 0cd43d1504cc2..37e1b015f0bbb 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -920,7 +920,7 @@ protected static function {$methodName}(\$container$lazyInitialization) $c = $this->addServiceInclude($id, $definition, null !== $isProxyCandidate); if ('' !== $c && $isProxyCandidate && !$definition->isShared()) { - $c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c))); + $c = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $c))); $code .= " static \$include = true;\n\n"; $code .= " if (\$include) {\n"; $code .= $c; @@ -933,7 +933,7 @@ protected static function {$methodName}(\$container$lazyInitialization) $c = $this->addInlineService($id, $definition); if (!$isProxyCandidate && !$definition->isShared()) { - $c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c))); + $c = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $c))); $lazyloadInitialization = $definition->isLazy() ? ', $lazyLoad = true' : ''; $c = sprintf(" %s = static function (\$container%s) {\n%s };\n\n return %1\$s(\$container);\n", $factory, $lazyloadInitialization, $c); @@ -1684,7 +1684,7 @@ private function wrapServiceConditionals(mixed $value, string $code): string } // re-indent the wrapped code - $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code))); + $code = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $code))); return sprintf(" if (%s) {\n%s }\n", $condition, $code); } @@ -1906,9 +1906,7 @@ private function dumpValue(mixed $value, bool $interpolate = true): string // the preg_replace_callback converts them to strings return $this->dumpParameter($match[1]); } else { - $replaceParameters = function ($match) { - return "'.".$this->dumpParameter($match[2]).".'"; - }; + $replaceParameters = fn ($match) => "'.".$this->dumpParameter($match[2]).".'"; $code = str_replace('%%', '%', preg_replace_callback('/(?export($value))); @@ -2183,7 +2181,7 @@ private function doExport(mixed $value, bool $resolveEnv = false): mixed } if (\is_string($value) && str_contains($value, "\n")) { $cleanParts = explode("\n", $value); - $cleanParts = array_map(function ($part) { return var_export($part, true); }, $cleanParts); + $cleanParts = array_map(fn ($part) => var_export($part, true), $cleanParts); $export = implode('."\n".', $cleanParts); } else { $export = var_export($value, true); diff --git a/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php b/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php index 05028781a340d..d0cc1f70b5939 100644 --- a/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php +++ b/src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php @@ -39,21 +39,11 @@ public function __construct(callable $serviceCompiler = null, \Closure $getEnv = public function getFunctions(): array { return [ - new ExpressionFunction('service', $this->serviceCompiler ?? function ($arg) { - return sprintf('$container->get(%s)', $arg); - }, function (array $variables, $value) { - return $variables['container']->get($value); - }), + new ExpressionFunction('service', $this->serviceCompiler ?? fn ($arg) => sprintf('$container->get(%s)', $arg), fn (array $variables, $value) => $variables['container']->get($value)), - new ExpressionFunction('parameter', function ($arg) { - return sprintf('$container->getParameter(%s)', $arg); - }, function (array $variables, $value) { - return $variables['container']->getParameter($value); - }), + new ExpressionFunction('parameter', fn ($arg) => sprintf('$container->getParameter(%s)', $arg), fn (array $variables, $value) => $variables['container']->getParameter($value)), - new ExpressionFunction('env', function ($arg) { - return sprintf('$container->getEnv(%s)', $arg); - }, function (array $variables, $value) { + new ExpressionFunction('env', fn ($arg) => sprintf('$container->getEnv(%s)', $arg), function (array $variables, $value) { if (!$this->getEnv) { throw new LogicException('You need to pass a getEnv closure to the expression langage provider to use the "env" function.'); } @@ -61,11 +51,7 @@ public function getFunctions(): array return ($this->getEnv)($value); }), - new ExpressionFunction('arg', function ($arg) { - return sprintf('$args?->get(%s)', $arg); - }, function (array $variables, $value) { - return $variables['args']?->get($value); - }), + new ExpressionFunction('arg', fn ($arg) => sprintf('$args?->get(%s)', $arg), fn (array $variables, $value) => $variables['args']?->get($value)), ]; } } diff --git a/src/Symfony/Component/DependencyInjection/Extension/ExtensionTrait.php b/src/Symfony/Component/DependencyInjection/Extension/ExtensionTrait.php index d920b848a9118..5bd88892fb9b3 100644 --- a/src/Symfony/Component/DependencyInjection/Extension/ExtensionTrait.php +++ b/src/Symfony/Component/DependencyInjection/Extension/ExtensionTrait.php @@ -40,7 +40,7 @@ private function executeConfiguratorCallback(ContainerBuilder $container, \Closu throw new \LogicException('Unable to create the ContainerConfigurator.'); } $bundleLoader->setCurrentDir(\dirname($file)); - $instanceof = &\Closure::bind(function &() { return $this->instanceof; }, $bundleLoader, $bundleLoader)(); + $instanceof = &\Closure::bind(fn &() => $this->instanceof, $bundleLoader, $bundleLoader)(); try { $callback(new ContainerConfigurator($container, $bundleLoader, $instanceof, $file, $file, $env)); diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php index 91acd9a10ea7d..f98ee506d0032 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configu 10000 rator/ContainerConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php @@ -51,7 +51,7 @@ public function __construct(ContainerBuilder $container, PhpFileLoader $loader, final public function extension(string $namespace, array $config) { if (!$this->container->hasExtension($namespace)) { - $extensions = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); + $extensions = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions())); throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $this->file, $namespace, $extensions ? implode('", "', $extensions) : 'none')); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php index e417f30bf8d85..65b1c2d0651e5 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php @@ -182,7 +182,7 @@ private function configBuilder(string $namespace): ConfigBuilderInterface } if (!$this->container->hasExtension($alias)) { - $extensions = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); + $extensions = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions())); throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s". Looked for namespace "%s", found "%s".', $namespace, $alias, $extensions ? implode('", "', $extensions) : 'none')); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 7c0ea3261300b..953a62a138b24 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -757,7 +757,7 @@ private function validateExtensions(\DOMDocument $dom, string $file) // can it be handled by an extension? if (!$this->container->hasExtension($node->namespaceURI)) { - $extensionNamespaces = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getNamespace(); }, $this->container->getExtensions())); + $extensionNamespaces = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getNamespace(), $this->container->getExtensions())); throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $node->tagName, $file, $node->namespaceURI, $extensionNamespaces ? implode('", "', $extensionNamespaces) : 'none')); } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index ad61c14437d1a..45b8c7df85d47 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -774,7 +774,7 @@ private function validate(mixed $content, string $file): ?array } if (!$this->container->hasExtension($namespace)) { - $extensionNamespaces = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); + $extensionNamespaces = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions())); throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $file, $namespace, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none')); } } diff --git a/src/Symfony/Component/DependencyInjection/ReverseContainer.php b/src/Symfony/Component/DependencyInjection/ReverseContainer.php index 9b1d331d85e63..22d1b35df1d06 100644 --- a/src/Symfony/Component/DependencyInjection/ReverseContainer.php +++ b/src/Symfony/Component/DependencyInjection/ReverseContainer.php @@ -31,9 +31,7 @@ public function __construct(Container $serviceContainer, ContainerInterface $rev $this->serviceContainer = $serviceContainer; $this->reversibleLocator = $reversibleLocator; $this->tagName = $tagName; - $this->getServiceId = \Closure::bind(function (object $service): ?string { - return array_search($service, $this->services, true) ?: array_search($service, $this->privates, true) ?: null; - }, $serviceContainer, Container::class); + $this->getServiceId = \Closure::bind(fn (object $service): ?string => array_search($service, $this->services, true) ?: array_search($service, $this->privates, true) ?: null, $serviceContainer, Container::class); } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveUnusedDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveUnusedDefinitionsPassTest.php index 80ae50ada8761..8bb125906bd01 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveUnusedDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RemoveUnusedDefinitionsPassTest.php @@ -143,9 +143,7 @@ public function testProcessDoesNotErrorOnServicesThatDoNotHaveDefinitions() public function testProcessWorksWithClosureErrorsInDefinitions() { $definition = new Definition(); - $definition->addError(function () { - return 'foo bar'; - }); + $definition->addError(fn () => 'foo bar'); $container = new ContainerBuilder(); $container diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php index e7bdb78615362..ffb05d78c78fc 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php @@ -304,9 +304,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('scalar_node_not_empty_validated') ->cannotBeEmpty() ->validate() - ->always(function ($value) { - return $value; - }) + ->always(fn ($value) => $value) ->end() ->end() ->integerNode('int_node')->end() @@ -314,8 +312,8 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('bool_node')->end() ->arrayNode('array_node') ->beforeNormalization() - ->ifTrue(function ($value) { return !\is_array($value); }) - ->then(function ($value) { return ['child_node' => $value]; }) + ->ifTrue(fn ($value) => !\is_array($value)) + ->then(fn ($value) => ['child_node' => $value]) ->end() ->beforeNormalization() ->ifArray() @@ -332,7 +330,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('bool_force_cast')->end() ->integerNode('int_unset_at_zero') ->validate() - ->ifTrue(function ($value) { return 0 === $value; }) + ->ifTrue(fn ($value) => 0 === $value) ->thenUnset() ->end() ->end() @@ -343,9 +341,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->variableNode('variable_node')->end() ->scalarNode('string_node') ->validate() - ->ifTrue(function ($value) { - return !\is_string($value) || 'fail' === $value; - }) + ->ifTrue(fn ($value) => !\is_string($value) || 'fail' === $value) ->thenInvalid('%s is not a valid string') ->end() ->end() diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 9ec5d0631fe61..c9cf9e5fd4410 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1037,9 +1037,7 @@ public function testCompilesClassDefinitionsOfLazyServices() $matchingResources = array_filter( $container->getResources(), - function (ResourceInterface $resource) { - return 'reflection.BarClass' === (string) $resource; - } + fn (ResourceInterface $resource) => 'reflection.BarClass' === (string) $resource ); $this->assertNotEmpty($matchingResources); diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php index 0ea6aa5679096..5d6435ec06d9c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php @@ -264,10 +264,10 @@ public function testGetEnvBase64() $this->assertSame('hello', $result); - $result = $processor->getEnv('base64', 'foo', function ($name) { return '/+0='; }); + $result = $processor->getEnv('base64', 'foo', fn ($name) => '/+0='); $this->assertSame("\xFF\xED", $result); - $result = $processor->getEnv('base64', 'foo', function ($name) { return '_-0='; }); + $result = $processor->getEnv('base64', 'foo', fn ($name) => '_-0='); $this->assertSame("\xFF\xED", $result); } @@ -509,9 +509,7 @@ public function testGetEnvEnumInvalidResolvedValue() $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Resolved value of "foo" did not result in a string or int value.'); - $processor->getEnv('enum', StringBackedEnum::class.':foo', function () { - return null; - }); + $processor->getEnv('enum', StringBackedEnum::class.':foo', fn () => null); } public function testGetEnvEnumInvalidArg() @@ -521,9 +519,7 @@ public function testGetEnvEnumInvalidArg() $this->expectException(RuntimeException::class); $this->expectExceptionMessage('"bogus" is not a "BackedEnum".'); - $processor->getEnv('enum', 'bogus:foo', function () { - return ''; - }); + $processor->getEnv('enum', 'bogus:foo', fn () => ''); } public function testGetEnvEnumInvalidBackedValue() @@ -533,9 +529,7 @@ public function testGetEnvEnumInvalidBackedValue() $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Enum value "bogus" is not backed by "'.StringBackedEnum::class.'".'); - $processor->getEnv('enum', StringBackedEnum::class.':foo', function () { - return 'bogus'; - }); + $processor->getEnv('enum', StringBackedEnum::class.':foo', fn () => 'bogus'); } /** @@ -569,9 +563,7 @@ public function testRequireMissingFile() $this->expectExceptionMessage('missing-file'); $processor = new EnvVarProcessor(new Container()); - $processor->getEnv('require', '/missing-file', function ($name) { - return $name; - }); + $processor->getEnv('require', '/missing-file', fn ($name) => $name); } public function testRequireFile() @@ -600,9 +592,7 @@ public function testGetEnvResolve($value, $processed) $processor = new EnvVarProcessor($container); - $result = $processor->getEnv('resolve', 'foo', function () { - return '%bar%'; - }); + $result = $processor->getEnv('resolve', 'foo', fn () => '%bar%'); $this->assertSame($processed, $result); } @@ -622,9 +612,7 @@ public function testGetEnvResolveNoMatch() { $processor = new EnvVarProcessor(new Container()); - $result = $processor->getEnv('resolve', 'foo', function () { - return '%%'; - }); + $result = $processor->getEnv('resolve', 'foo', fn () => '%%'); $this->assertSame('%', $result); } @@ -643,9 +631,7 @@ public function testGetEnvResolveNotScalar($value) $processor = new EnvVarProcessor($container); - $processor->getEnv('resolve', 'foo', function () { - return '%bar%'; - }); + $processor->getEnv('resolve', 'foo', fn () => '%bar%'); } public function notScalarResolve() @@ -665,9 +651,7 @@ public function testGetEnvResolveNestedEnv() $processor = new EnvVarProcessor($container); $getEnv = $processor->getEnv(...); - $result = $processor->getEnv('resolve', 'foo', function ($name) use ($getEnv) { - return 'foo' === $name ? '%env(BAR)%' : $getEnv('string', $name, function () {}); - }); + $result = $processor->getEnv('resolve', 'foo', fn ($name) => 'foo' === $name ? '%env(BAR)%' : $getEnv('string', $name, function () {})); $this->assertSame('BAR in container', $result); } @@ -683,9 +667,7 @@ public function testGetEnvResolveNestedRealEnv() $processor = new EnvVarProcessor($container); $getEnv = $processor->getEnv(...); - $result = $processor->getEnv('resolve', 'foo', function ($name) use ($getEnv) { - return 'foo' === $name ? '%env(BAR)%' : $getEnv('string', $name, function () {}); - }); + $result = $processor->getEnv('resolve', 'foo', fn ($name) => 'foo' === $name ? '%env(BAR)%' : $getEnv('string', $name, function () {})); $this->assertSame('BAR in environment', $result); @@ -835,9 +817,7 @@ public function testGetEnvInvalidPrefixWithDefault() */ public function testGetEnvUrlPath(?string $expected, string $url) { - $this->assertSame($expected, (new EnvVarProcessor(new Container()))->getEnv('url', 'foo', static function () use ($url): string { - return $url; - })['path']); + $this->assertSame($expected, (new EnvVarProcessor(new Container()))->getEnv('url', 'foo', static fn (): string => $url)['path']); } public function provideGetEnvUrlPath() diff --git a/src/Symfony/Component/DependencyInjection/Tests/LazyProxy/Instantiator/RealServiceInstantiatorTest.php b/src/Symfony/Component/DependencyInjection/Tests/LazyProxy/Instantiator/RealServiceInstantiatorTest.php index 4abbfdad3717f..544c046d0097b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/LazyProxy/Instantiator/RealServiceInstantiatorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/LazyProxy/Instantiator/RealServiceInstantiatorTest.php @@ -28,9 +28,7 @@ public function testInstantiateProxy() $instantiator = new RealServiceInstantiator(); $instance = new \stdClass(); $container = $this->createMock(ContainerInterface::class); - $callback = function () use ($instance) { - return $instance; - }; + $callback = fn () => $instance; $this->assertSame($instance, $instantiator->instantiateProxy($container, new Definition(), 'foo', $callback)); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 56a5fe0be4871..2404e8c0de234 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -1016,7 +1016,7 @@ public function testBindings() '$quz' => 'quz', '$factory' => 'factory', 'iterable $baz' => new TaggedIteratorArgument('bar'), - ], array_map(function (BoundArgument $v) { return $v->getValues()[0]; }, $definition->getBindings())); + ], array_map(fn (BoundArgument $v) => $v->getValues()[0], $definition->getBindings())); $this->assertEquals([ 'quz', null, @@ -1034,7 +1034,7 @@ public function testBindings() 'NonExistent' => null, '$quz' => 'quz', '$factory' => 'factory', - ], array_map(function (BoundArgument $v) { return $v->getValues()[0]; }, $definition->getBindings())); + ], array_map(fn (BoundArgument $v) => $v->getValues()[0], $definition->getBindings())); } public function testFqcnLazyProxy() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 9515bfee92099..48006a5842a7f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -913,7 +913,7 @@ public function testBindings() '$quz' => 'quz', '$factory' => 'factory', 'iterable $baz' => new TaggedIteratorArgument('bar'), - ], array_map(function (BoundArgument $v) { return $v->getValues()[0]; }, $definition->getBindings())); + ], array_map(fn (BoundArgument $v) => $v->getValues()[0], $definition->getBindings())); $this->assertEquals([ 'quz', null, @@ -931,7 +931,7 @@ public function testBindings() 'NonExistent' => null, '$quz' => 'quz', '$factory' => 'factory', - ], array_map(function (BoundArgument $v) { return $v->getValues()[0]; }, $definition->getBindings())); + ], array_map(fn (BoundArgument $v) => $v->getValues()[0], $definition->getBindings())); } public function testProcessNotExistingActionParam() diff --git a/src/Symfony/Component/DependencyInjection/Tests/ServiceLocatorTest.php b/src/Symfony/Component/DependencyInjection/Tests/ServiceLocatorTest.php index d8a3a0006dbc4..fdaefb1d23ab5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ServiceLocatorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ServiceLocatorTest.php @@ -32,8 +32,8 @@ public function testGetThrowsOnUndefinedService() $this->expectException(NotFoundExceptionInterface::class); $this->expectExceptionMessage('Service "dummy" not found: the container inside "Symfony\Component\DependencyInjection\Tests\ServiceLocatorTest" is a smaller service locator that only knows about the "foo" and "bar" services.'); $locator = $this->getServiceLocator([ - 'foo' => function () { return 'bar'; }, - 'bar' => function () { return 'baz'; }, + 'foo' => fn () => 'bar', + 'bar' => fn () => 'baz', ]); $locator->get('dummy'); @@ -74,8 +74,8 @@ public function testGetThrowsServiceNotFoundException() public function testInvoke() { $locator = $this->getServiceLocator([ - 'foo' => function () { return 'bar'; }, - 'bar' => function () { return 'baz'; }, + 'foo' => fn () => 'bar', + 'bar' => fn () => 'baz', ]); $this->assertSame('bar', $locator('foo')); @@ -86,9 +86,9 @@ public function testInvoke() public function testProvidesServicesInformation() { $locator = new ServiceLocator([ - 'foo' => function () { return 'bar'; }, - 'bar' => function (): string { return 'baz'; }, - 'baz' => function (): ?string { return 'zaz'; }, + 'foo' => fn () => 'bar', + 'bar' => fn (): string => 'baz', + 'baz' => fn (): ?string => 'zaz', ]); $this->assertSame($locator->getProvidedServices(), [ diff --git a/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php index 0fbd9196ff556..09388ad45982a 100644 --- a/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php @@ -262,9 +262,7 @@ public function testNormalizeWhiteSpace() public function testEach() { - $data = $this->createTestCrawler()->filterXPath('//ul[1]/li')->each(function ($node, $i) { - return $i.'-'.$node->text(); - }); + $data = $this->createTestCrawler()->filterXPath('//ul[1]/li')->each(fn ($node, $i) => $i.'-'.$node->text()); $this->assertEquals(['0-One', '1-Two', '2-Three'], $data, '->each() executes an anonymous function on each node of the list'); } @@ -290,9 +288,7 @@ public function testSlice() public function testReduce() { $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); - $nodes = $crawler->reduce(function ($node, $i) { - return 1 !== $i; - }); + $nodes = $crawler->reduce(fn ($node, $i) => 1 !== $i); $this->assertNotSame($nodes, $crawler, '->reduce() returns a new instance of a crawler'); $this->assertInstanceOf(Crawler::class, $nodes, '->reduce() returns a new instance of a crawler'); diff --git a/src/Symfony/Component/Dotenv/Command/DebugCommand.php b/src/Symfony/Component/Dotenv/Command/DebugCommand.php index fa76da97e9eb7..85cca991ca760 100644 --- a/src/Symfony/Component/Dotenv/Command/DebugCommand.php +++ b/src/Symfony/Component/Dotenv/Command/DebugCommand.php @@ -81,9 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $envFiles = $this->getEnvFiles(); - $availableFiles = array_filter($envFiles, function (string $file) { - return is_file($this->getFilePath($file)); - }); + $availableFiles = array_filter($envFiles, fn (string $file) => is_file($this->getFilePath($file))); if (\in_array('.env.local.php', $availableFiles, true)) { $io->warning('Due to existing dump file (.env.local.php) all other dotenv files are skipped.'); @@ -94,11 +92,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $io->section('Scanned Files (in descending priority)'); - $io->listing(array_map(static function (string $envFile) use ($availableFiles) { - return \in_array($envFile, $availableFiles, true) - ? sprintf('✓ %s', $envFile) - : sprintf('⨯ %s', $envFile); - }, $envFiles)); + $io->listing(array_map(static fn (string $envFile) => \in_array($envFile, $availableFiles, true) + ? sprintf('✓ %s', $envFile) + : sprintf('⨯ %s', $envFile), $envFiles)); $nameFilter = $input->getArgument('filter'); $variables = $this->getVariables($availableFiles, $nameFilter); diff --git a/src/Symfony/Component/ErrorHandler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php b/src/Symfony/Component/ErrorHandler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php index 153819f2a0804..2191e63bb02f1 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php @@ -140,7 +140,7 @@ private function convertFileToClass(string $path, string $file, string $prefix): ]; if ($prefix) { - $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return str_starts_with($candidate, $prefix); }); + $candidates = array_filter($candidates, fn ($candidate) => str_starts_with($candidate, $prefix)); } // We cannot use the autoloader here as most of them use require; but if the class diff --git a/src/Symfony/Component/ErrorHandler/ErrorHandler.php b/src/Symfony/Component/ErrorHandler/ErrorHandler.php index 0bf1ef6aecfe5..581bfbee917de 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorHandler.php +++ b/src/Symfony/Component/ErrorHandler/ErrorHandler.php @@ -722,8 +722,6 @@ private function cleanTrace(array $backtrace, int $type, string &$file, int &$li */ private function parseAnonymousClass(string $message): string { - return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) { - return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; - }, $message); + return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message); } } diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php index 57f34d8fcad4a..758048cb9444e 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php @@ -255,9 +255,7 @@ private function fileExcerpt(string $file, int $line, int $srcContext = 3): stri // remove main code/span tags $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); // split multiline spans - $code = preg_replace_callback('#]++)>((?:[^<]*+
    )++[^<]*+)
    #', function ($m) { - return "".str_replace('
    ', "

    ", $m[2]).''; - }, $code); + $code = preg_replace_callback('#]++)>((?:[^<]*+
    )++[^<]*+)
    #', fn ($m) => "".str_replace('
    ', "

    ", $m[2]).'', $code); $content = explode('
    ', $code); $lines = []; @@ -296,9 +294,7 @@ private function fixCodeMarkup(string $line) private function formatFileFromText(string $text) { - return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) { - return 'in '.$this->formatFile($match[2], $match[3]); - }, $text); + return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text); } private function formatLogMessage(string $message, array $context) diff --git a/src/Symfony/Component/ErrorHandler/Exception/FlattenException.php b/src/Symfony/Component/ErrorHandler/Exception/FlattenException.php index 19d69fee000fc..461c26812c047 100644 --- a/src/Symfony/Component/ErrorHandler/Exception/FlattenException.php +++ b/src/Symfony/Component/ErrorHandler/Exception/FlattenException.php @@ -195,9 +195,7 @@ public function getMessage(): string public function setMessage(string $message): static { if (str_contains($message, "@anonymous\0")) { - $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { - return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; - }, $message); + $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message); } $this->message = $message; diff --git a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php index ab2ea41a08c6c..f47973ccc4ff7 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php @@ -120,7 +120,7 @@ public function testClassAlias() */ public function testDeprecatedSuper(string $class, string $super, string $type) { - set_error_handler(function () { return false; }); + set_error_handler(fn () => false); $e = error_reporting(0); trigger_error('', E_USER_DEPRECATED); @@ -150,7 +150,7 @@ public function provideDeprecatedSuper(): array public function testInterfaceExtendsDeprecatedInterface() { - set_error_handler(function () { return false; }); + set_error_handler(fn () => false); $e = error_reporting(0); trigger_error('', E_USER_NOTICE); @@ -172,7 +172,7 @@ class_exists('Test\\'.NonDeprecatedInterfaceClass::class, true); public function testDeprecatedSuperInSameNamespace() { - set_error_handler(function () { return false; }); + set_error_handler(fn () => false); $e = error_reporting(0); trigger_error('', E_USER_NOTICE); @@ -242,7 +242,7 @@ class_exists(Fixtures\ExtendedFinalMethod::class, true); public function testExtendedDeprecatedMethodDoesntTriggerAnyNotice() { - set_error_handler(function () { return false; }); + set_error_handler(fn () => false); $e = error_reporting(0); trigger_error('', E_USER_NOTICE); diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php index 77a28ecde69d6..211ed3177568a 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php @@ -31,7 +31,7 @@ public function testSerializerContent() $exception = new \RuntimeException('Foo'); $errorRenderer = new SerializerErrorRenderer( new Serializer([new ProblemNormalizer()], [new JsonEncoder()]), - function () { return 'json'; } + fn () => 'json' ); $this->assertSame('{"type":"https:\/\/tools.ietf.org\/html\/rfc2616#section-10","title":"An error occurred","status":500,"detail":"Internal Server Error"}', $errorRenderer->render($exception)->getAsString()); diff --git a/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php index dce4658c35edf..a7b0cae437761 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php @@ -272,9 +272,7 @@ public function testToStringEmptyMessage() public function testToString() { - $test = function ($a, $b, $c, $d) { - return new \RuntimeException('This is a test message'); - }; + $test = fn ($a, $b, $c, $d) => new \RuntimeException('This is a test message'); $exception = $test('foo123', 1, null, 1.5); diff --git a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php index 51159d25078bc..6b7e074652e19 100644 --- a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php +++ b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php @@ -83,7 +83,7 @@ public function process(ContainerBuilder $container) $event['method'] = 'on'.preg_replace_callback([ '/(?<=\b|_)[a-z]/i', '/[^a-z0-9]/i', - ], function ($matches) { return strtoupper($matches[0]); }, $event['event']); + ], fn ($matches) => strtoupper($matches[0]), $event['event']); $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) { diff --git a/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php b/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php index a13e6f43f06f6..59f6fcbebfb37 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php @@ -347,7 +347,7 @@ public function testDispatchLazyListener() public function testRemoveFindsLazyListeners() { $test = new TestWithDispatcher(); - $factory = function () use ($test) { return $test; }; + $factory = fn () => $test; $this->dispatcher->addListener('foo', [$factory, 'foo']); $this->assertTrue($this->dispatcher->hasListeners('foo')); @@ -363,7 +363,7 @@ public function testRemoveFindsLazyListeners() public function testPriorityFindsLazyListeners() { $test = new TestWithDispatcher(); - $factory = function () use ($test) { return $test; }; + $factory = fn () => $test; $this->dispatcher->addListener('foo', [$factory, 'foo'], 3); $this->assertSame(3, $this->dispatcher->getListenerPriority('foo', [$test, 'foo'])); @@ -376,7 +376,7 @@ public function testPriorityFindsLazyListeners() public function testGetLazyListeners() { $test = new TestWithDispatcher(); - $factory = function () use ($test) { return $test; }; + $factory = fn () => $test; $this->dispatcher->addListener('foo', [$factory, 'foo'], 3); $this->assertSame([[$test, 'foo']], $this->dispatcher->getListeners('foo')); diff --git a/src/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php b/src/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php index 403bd7f4e913c..35bab1a9325da 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php @@ -75,7 +75,7 @@ public function testHasListenersDelegates() public function testAddListenerDisallowed() { $this->expectException(\BadMethodCallException::class); - $this->dispatcher->addListener('event', function () { return 'foo'; }); + $this->dispatcher->addListener('event', fn () => 'foo'); } public function testAddSubscriberDisallowed() @@ -89,7 +89,7 @@ public function testAddSubscriberDisallowed() public function testRemoveListenerDisallowed() { $this->expectException(\BadMethodCallException::class); - $this->dispatcher->removeListener('event', function () { return 'foo'; }); + $this->dispatcher->removeListener('event', fn () => 'foo'); } public function testRemoveSubscriberDisallowed() diff --git a/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php b/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php index e1f8ebc0919f1..d0ddd10f7d83f 100644 --- a/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php +++ b/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php @@ -82,13 +82,9 @@ public static function fromPhp(string $phpFunctionName, string $expressionFuncti throw new \InvalidArgumentException(sprintf('An expression function name must be defined when PHP function "%s" is namespaced.', $phpFunctionName)); } - $compiler = function (...$args) use ($phpFunctionName) { - return sprintf('\%s(%s)', $phpFunctionName, implode(', ', $args)); - }; + $compiler = fn (...$args) => sprintf('\%s(%s)', $phpFunctionName, implode(', ', $args)); - $evaluator = function ($p, ...$args) use ($phpFunctionName) { - return $phpFunctionName(...$args); - }; + $evaluator = fn ($p, ...$args) => $phpFunctionName(...$args); return new self($expressionFunctionName ?: end($parts), $compiler, $evaluator); } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/TestProvider.php b/src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/TestProvider.php index 339c03ff1267a..daffca32574b5 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/TestProvider.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/TestProvider.php @@ -19,11 +19,7 @@ class TestProvider implements ExpressionFunctionProviderInterface public function getFunctions(): array { return [ - new ExpressionFunction('identity', function ($input) { - return $input; - }, function (array $values, $input) { - return $input; - }), + new ExpressionFunction('identity', fn ($input) => $input, fn (array $values, $input) => $input), ExpressionFunction::fromPhp('strtoupper'), diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php index c6cb02c1b9a43..062b001c2e21d 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php @@ -41,12 +41,8 @@ public function getDumpData() protected function getCallables() { return [ - 'compiler' => function ($arg) { - return sprintf('foo(%s)', $arg); - }, - 'evaluator' => function ($variables, $arg) { - return $arg; - }, + 'compiler' => fn ($arg) => sprintf('foo(%s)', $arg), + 'evaluator' => fn ($variables, $arg) => $arg, ]; } } diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index c7f3dd29531f5..1ca852d29feac 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -438,11 +438,9 @@ public function makePathRelative(string $endPath, string $startPath): string $startPath = str_replace('\\', '/', $startPath); } - $splitDriveLetter = function ($path) { - return (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) - ? [substr($path, 2), strtoupper($path[0])] - : [$path, null]; - }; + $splitDriveLetter = fn ($path) => (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) + ? [substr($path, 2), strtoupper($path[0])] + : [$path, null]; $splitPath = function ($path) { $result = []; diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 7baecdf906260..76083f661e587 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -671,9 +671,7 @@ public function getIterator(): \Iterator $iterator = new \AppendIterator(); foreach ($this->dirs as $dir) { - $iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) { - return $this->searchInDirectory($dir); - }))); + $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir)))); } foreach ($this->iterators as $it) { diff --git a/src/Symfony/Component/Finder/Gitignore.php b/src/Symfony/Component/Finder/Gitignore.php index 070074b3ba85c..bf05c5b3793bb 100644 --- a/src/Symfony/Component/Finder/Gitignore.php +++ b/src/Symfony/Component/Finder/Gitignore.php @@ -79,9 +79,7 @@ private static function lineToRegex(string $gitignoreLine): string } $regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~'); - $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', function (array $matches): string { - return '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']'; - }, $regex); + $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', fn (array $matches): string => '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', $regex); $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_NAME_NATURAL === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { if ($a->isDir() && $b->isFile()) { @@ -74,29 +66,19 @@ public function __construct(\Traversable $iterator, int|callable $sort, bool $re return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getATime() - $b->getATime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getATime() - $b->getATime()); } elseif (self::SORT_BY_CHANGED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getCTime() - $b->getCTime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getCTime() - $b->getCTime()); } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getMTime() - $b->getMTime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getMTime() - $b->getMTime()); } elseif (self::SORT_BY_EXTENSION === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strnatcmp($a->getExtension(), $b->getExtension()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getExtension(), $b->getExtension()); } elseif (self::SORT_BY_SIZE === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getSize() - $b->getSize()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getSize() - $b->getSize()); } elseif (self::SORT_BY_NONE === $sort) { $this->sort = $order; } elseif (\is_callable($sort)) { - $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort(...); + $this->sort = $reverseOrder ? static fn (\SplFileInfo $a, \SplFileInfo $b) => -$sort($a, $b) : $sort(...); } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } diff --git a/src/Symfony/Component/Finder/Iterator/VcsIgnoredFilterIterator.php b/src/Symfony/Component/Finder/Iterator/VcsIgnoredFilterIterator.php index 29fc2d99b1af7..7e6051d38971a 100644 --- a/src/Symfony/Component/Finder/Iterator/VcsIgnoredFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/VcsIgnoredFilterIterator.php @@ -126,9 +126,7 @@ private function parentDirectoriesUpTo(string $from, string $upTo): array { return array_filter( $this->parentDirectoriesUpwards($from), - static function (string $directory) use ($upTo): bool { - return str_starts_with($directory, $upTo); - } + static fn (string $directory): bool => str_starts_with($directory, $upTo) ); } diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index 85d7c278e6a95..796cf7c47ddae 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -958,7 +958,7 @@ public function testSortByNameCaseInsensitive() public function testSort() { $finder = $this->buildFinder(); - $this->assertSame($finder, $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealPath(), $b->getRealPath()); })); + $this->assertSame($finder, $finder->sort(fn (\SplFileInfo $a, \SplFileInfo $b) => strcmp($a->getRealPath(), $b->getRealPath()))); $this->assertOrderedIterator($this->toAbsolute([ 'Zephire.php', 'foo', @@ -990,12 +990,8 @@ public function testSortAcrossDirectories() ]) ->depth(0) ->files() - ->filter(static function (\SplFileInfo $file): bool { - return '' !== $file->getExtension(); - }) - ->sort(static function (\SplFileInfo $a, \SplFileInfo $b): int { - return strcmp($a->getExtension(), $b->getExtension()) ?: strcmp($a->getFilename(), $b->getFilename()); - }) + ->filter(static fn (\SplFileInfo $file): bool => '' !== $file->getExtension()) + ->sort(static fn (\SplFileInfo $a, \SplFileInfo $b): int => strcmp($a->getExtension(), $b->getExtension()) ?: strcmp($a->getFilename(), $b->getFilename())) ; $this->assertOrderedIterator($this->toAbsolute([ @@ -1018,7 +1014,7 @@ public function testSortAcrossDirectories() public function testFilter() { $finder = $this->buildFinder(); - $this->assertSame($finder, $finder->filter(function (\SplFileInfo $f) { return str_contains($f, 'test'); })); + $this->assertSame($finder, $finder->filter(fn (\SplFileInfo $f) => str_contains($f, 'test'))); $this->assertIterator($this->toAbsolute(['test.php', 'test.py']), $finder->in(self::$tmpDir)->getIterator()); } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php index 7c3c65ce5ee81..8538dd9e62494 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php @@ -36,8 +36,8 @@ public function testAccept($filters, $expected) public function getAcceptData() { return [ - [[function (\SplFileInfo $fileinfo) { return false; }], []], - [[function (\SplFileInfo $fileinfo) { return str_starts_with($fileinfo, 'test'); }], ['test.php', 'test.py']], + [[fn (\SplFileInfo $fileinfo) => false], []], + [[fn (\SplFileInfo $fileinfo) => str_starts_with($fileinfo, 'test')], ['test.php', 'test.py']], [['is_dir'], []], ]; } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php b/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php index 71db60b48c9e3..e66f9362f09db 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php @@ -19,9 +19,9 @@ protected function assertIterator($expected, \Traversable $iterator) { // set iterator_to_array $use_key to false to avoid values merge // this made FinderTest::testAppendWithAnArray() fail with GnuFinderAdapter - $values = array_map(function (\SplFileInfo $fileinfo) { return str_replace('/', \DIRECTORY_SEPARATOR, $fileinfo->getPathname()); }, iterator_to_array($iterator, false)); + $values = array_map(fn (\SplFileInfo $fileinfo) => str_replace('/', \DIRECTORY_SEPARATOR, $fileinfo->getPathname()), iterator_to_array($iterator, false)); - $expected = array_map(function ($path) { return str_replace('/', \DIRECTORY_SEPARATOR, $path); }, $expected); + $expected = array_map(fn ($path) => str_replace('/', \DIRECTORY_SEPARATOR, $path), $expected); sort($values); sort($expected); @@ -31,8 +31,8 @@ protected function assertIterator($expected, \Traversable $iterator) protected function assertOrderedIterator($expected, \Traversable $iterator) { - $values = array_map(function (\SplFileInfo $fileinfo) { return str_replace('/', \DIRECTORY_SEPARATOR, $fileinfo->getPathname()); }, iterator_to_array($iterator)); - $expected = array_map(function ($path) { return str_replace('/', \DIRECTORY_SEPARATOR, $path); }, $expected); + $values = array_map(fn (\SplFileInfo $fileinfo) => str_replace('/', \DIRECTORY_SEPARATOR, $fileinfo->getPathname()), iterator_to_array($iterator)); + $expected = array_map(fn ($path) => str_replace('/', \DIRECTORY_SEPARATOR, $path), $expected); $this->assertEquals($expected, array_values($values)); } @@ -48,7 +48,7 @@ protected function assertOrderedIterator($expected, \Traversable $iterator) */ protected function assertOrderedIteratorForGroups(array $expected, \Traversable $iterator) { - $values = array_values(array_map(function (\SplFileInfo $fileinfo) { return $fileinfo->getPathname(); }, iterator_to_array($iterator))); + $values = array_values(array_map(fn (\SplFileInfo $fileinfo) => $fileinfo->getPathname(), iterator_to_array($iterator))); foreach ($expected as $subarray) { $temp = []; diff --git a/src/Symfony/Component/Finder/Tests/Iterator/LazyIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/LazyIteratorTest.php index 1a96c32d0b787..a32619cd4a618 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/LazyIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/LazyIteratorTest.php @@ -27,9 +27,7 @@ public function testLazy() public function testDelegate() { - $iterator = new LazyIterator(function () { - return new Iterator(['foo', 'bar']); - }); + $iterator = new LazyIterator(fn () => new Iterator(['foo', 'bar'])); $this->assertCount(2, $iterator); } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php b/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php index 670478d7a7524..5cee84c3b0c73 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php @@ -15,7 +15,7 @@ class MockFileListIterator extends \ArrayIterator { public function __construct(array $filesArray = []) { - $files = array_map(function ($file) { return new MockSplFileInfo($file); }, $filesArray); + $files = array_map(fn ($file) => new MockSplFileInfo($file), $filesArray); parent::__construct($files); } } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php index 182e58fbfe9c7..f8936f6f1b9e9 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php @@ -267,7 +267,7 @@ public function getAcceptData() [SortableIterator::SORT_BY_CHANGED_TIME, $this->toAbsolute($sortByChangedTime)], [SortableIterator::SORT_BY_MODIFIED_TIME, $this->toAbsolute($sortByModifiedTime)], [SortableIterator::SORT_BY_NAME_NATURAL, $this->toAbsolute($sortByNameNatural)], - [function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealPath(), $b->getRealPath()); }, $this->toAbsolute($customComparison)], + [fn (\SplFileInfo $a, \SplFileInfo $b) => strcmp($a->getRealPath(), $b->getRealPath()), $this->toAbsolute($customComparison)], ]; } } diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php index 524bbf4355cf7..8c25c19ccf49e 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php @@ -64,9 +64,7 @@ public function __construct(iterable $choices, callable $value = null) } if (null === $value && $this->castableToString($choices)) { - $value = function ($choice) { - return false === $choice ? '0' : (string) $choice; - }; + $value = fn ($choice) => false === $choice ? '0' : (string) $choice; } if (null !== $value) { diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index ef3769934167d..c417b84ac5755 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -35,9 +35,7 @@ public function createListFromChoices(iterable $choices, callable $value = null, if ($filter) { // filter the choice list lazily return $this->createListFromLoader(new FilterChoiceLoaderDecorator( - new CallbackChoiceLoader(static function () use ($choices) { - return $choices; - } + new CallbackChoiceLoader(static fn () => $choices ), $filter), $value); } @@ -67,9 +65,7 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC } else { // make sure we have keys that reflect order $preferredChoices = array_values($preferredChoices); - $preferredChoices = static function ($choice) use ($preferredChoices) { - return array_search($choice, $preferredChoices, true); - }; + $preferredChoices = static fn ($choice) => array_search($choice, $preferredChoices, true); } } @@ -137,11 +133,9 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC ); } - uksort($preferredViews, static function ($a, $b) use ($preferredViewsOrder): int { - return isset($preferredViewsOrder[$a], $preferredViewsOrder[$b]) - ? $preferredViewsOrder[$a] <=> $preferredViewsOrder[$b] - : 0; - }); + uksort($preferredViews, static fn ($a, $b): int => isset($preferredViewsOrder[$a], $preferredViewsOrder[$b]) + ? $preferredViewsOrder[$a] <=> $preferredViewsOrder[$b] + : 0); return new ChoiceListView($otherViews, $preferredViews); } diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php index 2f29bda726cab..b0b8e479d8aba 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -63,13 +63,11 @@ public function createListFromChoices(iterable $choices, mixed $value = null, mi if ($value instanceof PropertyPathInterface) { $accessor = $this->propertyAccessor; - $value = function ($choice) use ($accessor, $value) { - // The callable may be invoked with a non-object/array value - // when such values are passed to - // ChoiceListInterface::getValuesForChoices(). Handle this case - // so that the call to getValue() doesn't break. - return \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null; - }; + // The callable may be invoked with a non-object/array value + // when such values are passed to + // ChoiceListInterface::getValuesForChoices(). Handle this case + // so that the call to getValue() doesn't break. + $value = fn ($choice) => \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null; } if (\is_string($filter)) { @@ -78,9 +76,7 @@ public function createListFromChoices(iterable $choices, mixed $value = null, mi if ($filter instanceof PropertyPath) { $accessor = $this->propertyAccessor; - $filter = static function ($choice) use ($accessor, $filter) { - return (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter); - }; + $filter = static fn ($choice) => (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter); } return $this->decoratedFactory->createListFromChoices($choices, $value, $filter); @@ -94,13 +90,11 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, mixed $value if ($value instanceof PropertyPathInterface) { $accessor = $this->propertyAccessor; - $value = function ($choice) use ($accessor, $value) { - // The callable may be invoked with a non-object/array value - // when such values are passed to - // ChoiceListInterface::getValuesForChoices(). Handle this case - // so that the call to getValue() doesn't break. - return \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null; - }; + // The callable may be invoked with a non-object/array value + // when such values are passed to + // ChoiceListInterface::getValuesForChoices(). Handle this case + // so that the call to getValue() doesn't break. + $value = fn ($choice) => \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null; } if (\is_string($filter)) { @@ -109,9 +103,7 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, mixed $value if ($filter instanceof PropertyPath) { $accessor = $this->propertyAccessor; - $filter = static function ($choice) use ($accessor, $filter) { - return (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter); - }; + $filter = static fn ($choice) => (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter); } return $this->decoratedFactory->createListFromLoader($loader, $value, $filter); @@ -126,9 +118,7 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = } if ($label instanceof PropertyPathInterface) { - $label = function ($choice) use ($accessor, $label) { - return $accessor->getValue($choice, $label); - }; + $label = fn ($choice) => $accessor->getValue($choice, $label); } if (\is_string($preferredChoices)) { @@ -151,9 +141,7 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = } if ($index instanceof PropertyPathInterface) { - $index = function ($choice) use ($accessor, $index) { - return $accessor->getValue($choice, $index); - }; + $index = fn ($choice) => $accessor->getValue($choice, $index); } if (\is_string($groupBy)) { @@ -176,9 +164,7 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = } if ($attr instanceof PropertyPathInterface) { - $attr = function ($choice) use ($accessor, $attr) { - return $accessor->getValue($choice, $attr); - }; + $attr = fn ($choice) => $accessor->getValue($choice, $attr); } if (\is_string($labelTranslationParameters)) { @@ -186,9 +172,7 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = } if ($labelTranslationParameters instanceof PropertyPath) { - $labelTranslationParameters = static function ($choice) use ($accessor, $labelTranslationParameters) { - return $accessor->getValue($choice, $labelTranslationParameters); - }; + $labelTranslationParameters = static fn ($choice) => $accessor->getValue($choice, $labelTranslationParameters); } return $this->decoratedFactory->createView( diff --git a/src/Symfony/Component/Form/Command/DebugCommand.php b/src/Symfony/Component/Form/Command/DebugCommand.php index 0aadd4347a8f9..a4975470c9b79 100644 --- a/src/Symfony/Component/Form/Command/DebugCommand.php +++ b/src/Symfony/Component/Form/Command/DebugCommand.php @@ -204,7 +204,7 @@ private function getCoreTypes(): array $coreExtension = new CoreExtension(); $loadTypesRefMethod = (new \ReflectionObject($coreExtension))->getMethod('loadTypes'); $coreTypes = $loadTypesRefMethod->invoke($coreExtension); - $coreTypes = array_map(function (FormTypeInterface $type) { return $type::class; }, $coreTypes); + $coreTypes = array_map(fn (FormTypeInterface $type) => $type::class, $coreTypes); sort($coreTypes); return $coreTypes; @@ -237,7 +237,7 @@ private function findAlternatives(string $name, array $collection): array } $threshold = 1e3; - $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold); ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); return array_keys($alternatives); diff --git a/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php index 53151574bdeb0.. 10000 317287f174478 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php @@ -35,9 +35,7 @@ protected function describeDefaults(array $options) { if ($options['core_types']) { $this->output->section('Built-in form types (Symfony\Component\Form\Extension\Core\Type)'); - $shortClassNames = array_map(function ($fqcn) { - return $this->formatClassLink($fqcn, \array_slice(explode('\\', $fqcn), -1)[0]); - }, $options['core_types']); + $shortClassNames = array_map(fn ($fqcn) => $this->formatClassLink($fqcn, \array_slice(explode('\\', $fqcn), -1)[0]), $options['core_types']); for ($i = 0, $loopsMax = \count($shortClassNames); $i * 5 < $loopsMax; ++$i) { $this->output->writeln(' '.implode(', ', \array_slice($shortClassNames, $i * 5, 5))); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php index d513694a4ccf8..b9f5c3f478305 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php @@ -42,9 +42,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) public function configureOptions(OptionsResolver $resolver) { - $emptyData = function (FormInterface $form, $viewData) { - return $viewData; - }; + $emptyData = fn (FormInterface $form, $viewData) => $viewData; $resolver->setDefaults([ 'value' => '1', @@ -52,9 +50,7 @@ public function configureOptions(OptionsResolver $resolver) 'compound' => false, 'false_values' => [null], 'invalid_message' => 'The checkbox has an invalid value.', - 'is_empty_callback' => static function ($modelData): bool { - return false === $modelData; - }, + 'is_empty_callback' => static fn ($modelData): bool => false === $modelData, ]); $resolver->setAllowedTypes('false_values', 'array'); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 65692faeb48e8..051795b9571da 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -245,13 +245,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) // closure here that is optimized for the value of the form, to // avoid making the type check inside the closure. if ($options['multiple']) { - $view->vars['is_selected'] = function ($choice, array $values) { - return \in_array($choice, $values, true); - }; + $view->vars['is_selected'] = fn ($choice, array $values) => \in_array($choice, $values, true); } else { - $view->vars['is_selected'] = function ($choice, $value) { - return $choice === $value; - }; + $view->vars['is_selected'] = fn ($choice, $value) => $choice === $value; } // Check if the choices already contain the empty value @@ -301,9 +297,7 @@ public function configureOptions(OptionsResolver $resolver) return ''; }; - $placeholderDefault = function (Options $options) { - return $options['required'] ? null : ''; - }; + $placeholderDefault = fn (Options $options) => $options['required'] ? null : ''; $placeholderNormalizer = function (Options $options, $placeholder) { if ($options['multiple']) { @@ -324,9 +318,7 @@ public function configureOptions(OptionsResolver $resolver) return $placeholder; }; - $compound = function (Options $options) { - return $options['expanded']; - }; + $compound = fn (Options $options) => $options['expanded']; $choiceTranslationDomainNormalizer = function (Options $options, $choiceTranslationDomain) { if (true === $choiceTranslationDomain) { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php index 25aa652f5cc72..69f284c8233f4 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php @@ -33,9 +33,7 @@ public function configureOptions(OptionsResolver $resolver) $choiceTranslationLocale = $options['choice_translation_locale']; $alpha3 = $options['alpha3']; - return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale, $alpha3) { - return array_flip($alpha3 ? Countries::getAlpha3Names($choiceTranslationLocale) : Countries::getNames($choiceTranslationLocale)); - }), [$choiceTranslationLocale, $alpha3]); + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(fn () => array_flip($alpha3 ? Countries::getAlpha3Names($choiceTranslationLocale) : Countries::getNames($choiceTranslationLocale))), [$choiceTranslationLocale, $alpha3]); }, 'choice_translation_domain' => false, 'choice_translation_locale' => null, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php index 435bc157091bc..f697f35416225 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php @@ -32,9 +32,7 @@ public function configureOptions(OptionsResolver $resolver) $choiceTranslationLocale = $options['choice_translation_locale']; - return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) { - return array_flip(Currencies::getNames($choiceTranslationLocale)); - }), $choiceTranslationLocale); + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(fn () => array_flip(Currencies::getNames($choiceTranslationLocale))), $choiceTranslationLocale); }, 'choice_translation_domain' => false, 'choice_translation_locale' => null, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php index e745c1af045ee..db1a14b52b74d 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php @@ -159,16 +159,10 @@ public function buildView(FormView $view, FormInterface $form, array $options) public function configureOptions(OptionsResolver $resolver) { - $compound = function (Options $options) { - return 'single_text' !== $options['widget']; - }; - $emptyData = function (Options $options) { - return 'single_text' === $options['widget'] ? '' : []; - }; + $compound = fn (Options $options) => 'single_text' !== $options['widget']; + $emptyData = fn (Options $options) => 'single_text' === $options['widget'] ? '' : []; - $placeholderDefault = function (Options $options) { - return $options['required'] ? null : ''; - }; + $placeholderDefault = fn (Options $options) => $options['required'] ? null : ''; $placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) { if (\is_array($placeholder)) { @@ -180,20 +174,16 @@ public function configureOptions(OptionsResolver $resolver) return array_fill_keys(self::TIME_PARTS, $placeholder); }; - $labelsNormalizer = function (Options $options, array $labels) { - return array_replace([ - 'years' => null, - 'months' => null, - 'days' => null, - 'weeks' => null, - 'hours' => null, - 'minutes' => null, - 'seconds' => null, - 'invert' => 'Negative interval', - ], array_filter($labels, function ($label) { - return null !== $label; - })); - }; + $labelsNormalizer = fn (Options $options, array $labels) => array_replace([ + 'years' => null, + 'months' => null, + 'days' => null, + 'weeks' => null, + 'hours' => null, + 'minutes' => null, + 'seconds' => null, + 'invert' => 'Negative interval', + ], array_filter($labels, fn ($label) => null !== $label)); $resolver->setDefaults([ 'with_years' => true, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index 35022530e6873..bf7a734a9b2c5 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -107,12 +107,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) ])); if ($emptyData instanceof \Closure) { - $lazyEmptyData = static function ($option) use ($emptyData) { - return static function (FormInterface $form) use ($emptyData, $option) { - $emptyData = $emptyData($form->getParent()); + $lazyEmptyData = static fn ($option) => static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); - return $emptyData[$option] ?? ''; - }; + return $emptyData[$option] ?? ''; }; $dateOptions['empty_data'] = $lazyEmptyData('date'); @@ -222,19 +220,13 @@ public function buildView(FormView $view, FormInterface $form, array $options) public function configureOptions(OptionsResolver $resolver) { - $compound = function (Options $options) { - return 'single_text' !== $options['widget']; - }; + $compound = fn (Options $options) => 'single_text' !== $options['widget']; // Defaults to the value of "widget" - $dateWidget = function (Options $options) { - return 'single_text' === $options['widget'] ? null : $options['widget']; - }; + $dateWidget = fn (Options $options) => 'single_text' === $options['widget'] ? null : $options['widget']; // Defaults to the value of "widget" - $timeWidget = function (Options $options) { - return 'single_text' === $options['widget'] ? null : $options['widget']; - }; + $timeWidget = fn (Options $options) => 'single_text' === $options['widget'] ? null : $options['widget']; $resolver->setDefaults([ 'input' => 'datetime', @@ -260,9 +252,7 @@ public function configureOptions(OptionsResolver $resolver) 'compound' => $compound, 'date_label' => null, 'time_label' => null, - 'empty_data' => function (Options $options) { - return $options['compound'] ? [] : ''; - }, + 'empty_data' => fn (Options $options) => $options['compound'] ? [] : '', 'input_format' => 'Y-m-d H:i:s', 'invalid_message' => 'Please enter a valid date and time.', ]); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index d88819ebd673e..b18df900ff68e 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -81,12 +81,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) $emptyData = $builder->getEmptyData() ?: []; if ($emptyData instanceof \Closure) { - $lazyEmptyData = static function ($option) use ($emptyData) { - return static function (FormInterface $form) use ($emptyData, $option) { - $emptyData = $emptyData($form->getParent()); + $lazyEmptyData = static fn ($option) => static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); - return $emptyData[$option] ?? ''; - }; + return $emptyData[$option] ?? ''; }; $yearOptions['empty_data'] = $lazyEmptyData('year'); @@ -218,13 +216,9 @@ public function finishView(FormView $view, FormInterface $form, array $options) public function configureOptions(OptionsResolver $resolver) { - $compound = function (Options $options) { - return 'single_text' !== $options['widget']; - }; + $compound = fn (Options $options) => 'single_text' !== $options['widget']; - $placeholderDefault = function (Options $options) { - return $options['required'] ? null : ''; - }; + $placeholderDefault = fn (Options $options) => $options['required'] ? null : ''; $placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) { if (\is_array($placeholder)) { @@ -260,9 +254,7 @@ public function configureOptions(OptionsResolver $resolver) ]; }; - $format = function (Options $options) { - return 'single_text' === $options['widget'] ? self::HTML5_FORMAT : self::DEFAULT_FORMAT; - }; + $format = fn (Options $options) => 'single_text' === $options['widget'] ? self::HTML5_FORMAT : self::DEFAULT_FORMAT; $resolver->setDefaults([ 'years' => range((int) date('Y') - 5, (int) date('Y') + 5), @@ -285,9 +277,7 @@ public function configureOptions(OptionsResolver $resolver) // this option. 'data_class' => null, 'compound' => $compound, - 'empty_data' => function (Options $options) { - return $options['compound'] ? [] : ''; - }, + 'empty_data' => fn (Options $options) => $options['compound'] ? [] : '', 'choice_translation_domain' => false, 'input_format' => 'Y-m-d', 'invalid_message' => 'Please enter a valid date.', diff --git a/src/Symfony/Component/Form/Extension/Core/Type/EnumType.php b/src/Symfony/Component/Form/Extension/Core/Type/EnumType.php index 33944c2aec7c4..003819e065738 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/EnumType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/EnumType.php @@ -28,12 +28,8 @@ public function configureOptions(OptionsResolver $resolver): void ->setRequired(['class']) ->setAllowedTypes('class', 'string') ->setAllowedValues('class', enum_exists(...)) - ->setDefault('choices', static function (Options $options): array { - return $options['class']::cases(); - }) - ->setDefault('choice_label', static function (\UnitEnum $choice): string { - return $choice->name; - }) + ->setDefault('choices', static fn (Options $options): array => $options['class']::cases()) + ->setDefault('choice_label', static fn (\UnitEnum $choice): string => $choice->name) ->setDefault('choice_value', static function (Options $options): ?\Closure { if (!is_a($options['class'], \BackedEnum::class, true)) { return null; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 9e8e81394e8ce..d1bad77505c69 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -104,14 +104,10 @@ public function configureOptions(OptionsResolver $resolver) { $dataClass = null; if (class_exists(File::class)) { - $dataClass = function (Options $options) { - return $options['multiple'] ? null : File::class; - }; + $dataClass = fn (Options $options) => $options['multiple'] ? null : File::class; } - $emptyData = function (Options $options) { - return $options['multiple'] ? [] : null; - }; + $emptyData = fn (Options $options) => $options['multiple'] ? [] : null; $resolver->setDefaults([ 'compound' => false, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index e7ae6373bad01..c08d5f9b522ce 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -134,37 +134,25 @@ public function configureOptions(OptionsResolver $resolver) parent::configureOptions($resolver); // Derive "data_class" option from passed "data" object - $dataClass = function (Options $options) { - return isset($options['data']) && \is_object($options['data']) ? $options['data']::class : null; - }; + $dataClass = fn (Options $options) => isset($options['data']) && \is_object($options['data']) ? $options['data']::class : null; // Derive "empty_data" closure from "data_class" option $emptyData = function (Options $options) { $class = $options['data_class']; if (null !== $class) { - return function (FormInterface $form) use ($class) { - return $form->isEmpty() && !$form->isRequired() ? null : new $class(); - }; + return fn (FormInterface $form) => $form->isEmpty() && !$form->isRequired() ? null : new $class(); } - return function (FormInterface $form) { - return $form->getConfig()->getCompound() ? [] : ''; - }; + return fn (FormInterface $form) => $form->getConfig()->getCompound() ? [] : ''; }; // Wrap "post_max_size_message" in a closure to translate it lazily - $uploadMaxSizeMessage = function (Options $options) { - return function () use ($options) { - return $options['post_max_size_message']; - }; - }; + $uploadMaxSizeMessage = fn (Options $options) => fn () => $options['post_max_size_message']; // For any form that is not represented by a single HTML control, // errors should bubble up by default - $errorBubbling = function (Options $options) { - return $options['compound'] && !$options['inherit_data']; - }; + $errorBubbling = fn (Options $options) => $options['compound'] && !$options['inherit_data']; // If data is given, the form is locked to that data // (independent of its value) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php index d4adca1f5993a..0cdde9e46bb25 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php @@ -32,9 +32,7 @@ public function configureOptions(OptionsResolver $resolver) $choiceTranslationLocale = $options['choice_translation_locale']; - return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) { - return array_flip(Locales::getNames($choiceTranslationLocale)); - }), $choiceTranslationLocale); + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(fn () => array_flip(Locales::getNames($choiceTranslationLocale))), $choiceTranslationLocale); }, 'choice_translation_domain' => false, 'choice_translation_locale' => null, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index 2b8bbb5010214..78929d79bf464 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -96,12 +96,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) $emptyData = $builder->getEmptyData() ?: []; if ($emptyData instanceof \Closure) { - $lazyEmptyData = static function ($option) use ($emptyData) { - return static function (FormInterface $form) use ($emptyData, $option) { - $emptyData = $emptyData($form->getParent()); + $lazyEmptyData = static fn ($option) => static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); - return $emptyData[$option] ?? ''; - }; + return $emptyData[$option] ?? ''; }; $hourOptions['empty_data'] = $lazyEmptyData('hour'); @@ -235,13 +233,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) public function configureOptions(OptionsResolver $resolver) { - $compound = function (Options $options) { - return 'single_text' !== $options['widget']; - }; + $compound = fn (Options $options) => 'single_text' !== $options['widget']; - $placeholderDefault = function (Options $options) { - return $options['required'] ? null : ''; - }; + $placeholderDefault = fn (Options $options) => $options['required'] ? null : ''; $placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) { if (\is_array($placeholder)) { @@ -324,9 +318,7 @@ public function configureOptions(OptionsResolver $resolver) // representation is not \DateTime, but an array, we need to unset // this option. 'data_class' => null, - 'empty_data' => function (Options $options) { - return $options['compound'] ? [] : ''; - }, + 'empty_data' => fn (Options $options) => $options['compound'] ? [] : '', 'compound' => $compound, 'choice_translation_domain' => false, 'invalid_message' => 'Please enter a valid time.', diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index edea6678e2ef0..d6ce3c3435e22 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -48,14 +48,10 @@ public function configureOptions(OptionsResolver $resolver) $choiceTranslationLocale = $options['choice_translation_locale']; - return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($input, $choiceTranslationLocale) { - return self::getIntlTimezones($input, $choiceTranslationLocale); - }), [$input, $choiceTranslationLocale]); + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(fn () => self::getIntlTimezones($input, $choiceTranslationLocale)), [$input, $choiceTranslationLocale]); } - return ChoiceList::lazy($this, function () use ($input) { - return self::getPhpTimezones($input); - }, $input); + return ChoiceList::lazy($this, fn () => self::getPhpTimezones($input), $input); }, 'choice_translation_domain' => false, 'choice_translation_locale' => null, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php b/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php index 760bb880b1bd0..5b05e0a310544 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php @@ -91,13 +91,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) public function configureOptions(OptionsResolver $resolver) { - $compound = function (Options $options) { - return 'single_text' !== $options['widget']; - }; + $compound = fn (Options $options) => 'single_text' !== $options['widget']; - $placeholderDefault = function (Options $options) { - return $options['required'] ? null : ''; - }; + $placeholderDefault = fn (Options $options) => $options['required'] ? null : ''; $placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) { if (\is_array($placeholder)) { @@ -137,13 +133,9 @@ public function configureOptions(OptionsResolver $resolver) 'widget' => 'single_text', 'input' => 'array', 'placeholder' => $placeholderDefault, - 'html5' => static function (Options $options) { - return 'single_text' === $options['widget']; - }, + 'html5' => static fn (Options $options) => 'single_text' === $options['widget'], 'error_bubbling' => false, - 'empty_data' => function (Options $options) { - return $options['compound'] ? [] : ''; - }, + 'empty_data' => fn (Options $options) => $options['compound'] ? [] : '', 'compound' => $compound, 'choice_translation_domain' => false, 'invalid_message' => 'Please enter a valid week.', diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php index 0fca88069b066..cdc13c9d0394c 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php @@ -229,20 +229,16 @@ protected function getCasters(): array return $a; }, - FormInterface::class => function (FormInterface $f, array $a) { - return [ - Caster::PREFIX_VIRTUAL.'name' => $f->getName(), - Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub($f->getConfig()->getType()->getInnerType()::class), - ]; - }, + FormInterface::class => fn (FormInterface $f, array $a) => [ + Caster::PREFIX_VIRTUAL.'name' => $f->getName(), + Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub($f->getConfig()->getType()->getInnerType()::class), + ], FormView::class => StubCaster::cutInternals(...), - ConstraintViolationInterface::class => function (ConstraintViolationInterface $v, array $a) { - return [ - Caster::PREFIX_VIRTUAL.'root' => $v->getRoot(), - Caster::PREFIX_VIRTUAL.'path' => $v->getPropertyPath(), - Caster::PREFIX_VIRTUAL.'value' => $v->getInvalidValue(), - ]; - }, + ConstraintViolationInterface::class => fn (ConstraintViolationInterface $v, array $a) => [ + Caster::PREFIX_VIRTUAL.'root' => $v->getRoot(), + Caster::PREFIX_VIRTUAL.'path' => $v->getPropertyPath(), + Caster::PREFIX_VIRTUAL.'value' => $v->getInvalidValue(), + ], ]; } diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php index 26653dc9985b0..b28cf870b0c23 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -47,9 +47,7 @@ public function configureOptions(OptionsResolver $resolver) parent::configureOptions($resolver); // Constraint should always be converted to an array - $constraintsNormalizer = function (Options $options, $constraints) { - return \is_object($constraints) ? [$constraints] : (array) $constraints; - }; + $constraintsNormalizer = fn (Options $options, $constraints) => \is_object($constraints) ? [$constraints] : (array) $constraints; $resolver->setDefaults([ 'error_mapping' => [], diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php index 664a3edae2766..0e8a4ccf14fbc 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php @@ -24,9 +24,7 @@ class RepeatedTypeValidatorExtension extends AbstractTypeExtension public function configureOptions(OptionsResolver $resolver) { // Map errors to the first field - $errorMapping = function (Options $options) { - return ['.' => $options['first_name']]; - }; + $errorMapping = fn (Options $options) => ['.' => $options['first_name']]; $resolver->setDefaults([ 'error_mapping' => $errorMapping, diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php index 14f6c8f2d8b7e..c720e6a0ecbd0 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php @@ -36,11 +36,7 @@ public function configureOptions(OptionsResolver $resolver) { $translator = $this->translator; $translationDomain = $this->translationDomain; - $resolver->setNormalizer('upload_max_size_message', function (Options $options, $message) use ($translator, $translationDomain) { - return function () use ($translator, $translationDomain, $message) { - return $translator->trans($message(), [], $translationDomain); - }; - }); + $resolver->setNormalizer('upload_max_size_message', fn (Options $options, $message) => fn () => $translator->trans($message(), [], $translationDomain)); } public static function getExtendedTypes(): iterable diff --git a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php index 6fb47884d2e21..4d65d071ee563 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php +++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php @@ -66,9 +66,7 @@ public function __construct(MetadataFactoryInterface $metadataFactory) public function guessType(string $class, string $property): ?TypeGuess { - return $this->guess($class, $property, function (Constraint $constraint) { - return $this->guessTypeForConstraint($constraint); - }); + return $this->guess($class, $property, fn (Constraint $constraint) => $this->guessTypeForConstraint($constraint)); } public function guessRequired(string $class, string $property): ?ValueGuess @@ -82,16 +80,12 @@ public function guessRequired(string $class, string $property): ?ValueGuess public function guessMaxLength(string $class, string $property): ?ValueGuess { - return $this->guess($class, $property, function (Constraint $constraint) { - return $this->guessMaxLengthForConstraint($constraint); - }); + return $this->guess($class, $property, fn (Constraint $constraint) => $this->guessMaxLengthForConstraint($constraint)); } public function guessPattern(string $class, string $property): ?ValueGuess { - return $this->guess($class, $property, function (Constraint $constraint) { - return $this->guessPatternForConstraint($constraint); - }); + return $this->guess($class, $property, fn (Constraint $constraint) => $this->guessPatternForConstraint($constraint)); } /** diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 7acf8c7251a3c..9d6344c8695e6 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -930,9 +930,7 @@ private function sort(array &$children): void return; } - uksort($children, static function ($a, $b) use ($c): int { - return [$c[$b]['p'], $c[$a]['i']] <=> [$c[$a]['p'], $c[$b]['i']]; - }); + uksort($children, static fn ($a, $b): int => [$c[$b]['p'], $c[$a]['i']] <=> [$c[$a]['p'], $c[$b]['i']]); } /** diff --git a/src/Symfony/Component/Form/FormTypeGuesserChain.php b/src/Symfony/Component/Form/FormTypeGuesserChain.php index 7d8f617f7baaa..2ac8e3c263ab1 100644 --- a/src/Symfony/Component/Form/FormTypeGuesserChain.php +++ b/src/Symfony/Component/Form/FormTypeGuesserChain.php @@ -45,30 +45,22 @@ public function __construct(iterable $guessers) public function guessType(string $class, string $property): ?TypeGuess { - return $this->guess(function ($guesser) use ($class, $property) { - return $guesser->guessType($class, $property); - }); + return $this->guess(fn ($guesser) => $guesser->guessType($class, $property)); } public function guessRequired(string $class, string $property): ?ValueGuess { - return $this->guess(function ($guesser) use ($class, $property) { - return $guesser->guessRequired($class, $property); - }); + return $this->guess(fn ($guesser) => $guesser->guessRequired($class, $property)); } public function guessMaxLength(string $class, string $property): ?ValueGuess { - return $this->guess(function ($guesser) use ($class, $property) { - return $guesser->guessMaxLength($class, $property); - }); + return $this->guess(fn ($guesser) => $guesser->guessMaxLength($class, $property)); } public function guessPattern(string $class, string $property): ?ValueGuess { - return $this->guess(function ($guesser) use ($class, $property) { - return $guesser->guessPattern($class, $property); - }); + return $this->guess(fn ($guesser) => $guesser->guessPattern($class, $property)); } /** diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index 6a38a77017dba..23b90ebf7629c 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -763,9 +763,7 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => false, 'expanded' => true, ]); @@ -840,9 +838,7 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => true, 'expanded' => true, ]); diff --git a/src/Symfony/Component/Form/Tests/CallbackTransformerTest.php b/src/Symfony/Component/Form/Tests/CallbackTransformerTest.php index 9142e1fa3c5e3..1fb6134dd9592 100644 --- a/src/Symfony/Component/Form/Tests/CallbackTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/CallbackTransformerTest.php @@ -19,8 +19,8 @@ class CallbackTransformerTest extends TestCase public function testTransform() { $transformer = new CallbackTransformer( - function ($value) { return $value.' has been transformed'; }, - function ($value) { return $value.' has reversely been transformed'; } + fn ($value) => $value.' has been transformed', + fn ($value) => $value.' has reversely been transformed' ); $this->assertEquals('foo has been transformed', $transformer->transform('foo')); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php index ce5f2e933f00d..5dc2e5db909eb 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php @@ -45,9 +45,7 @@ protected function getValues() public function testCreateChoiceListWithValueCallback() { - $callback = function ($choice) { - return ':'.$choice; - }; + $callback = fn ($choice) => ':'.$choice; $choiceList = new ArrayChoiceList([2 => 'foo', 7 => 'bar', 10 => 'baz'], $callback); @@ -112,9 +110,7 @@ public function testCreateChoiceListWithGroupedChoices() public function testCompareChoicesByIdentityByDefault() { - $callback = function ($choice) { - return $choice->value; - }; + $callback = fn ($choice) => $choice->value; $obj1 = (object) ['value' => 'value1']; $obj2 = (object) ['value' => 'value2']; diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/Cache/ChoiceLoaderTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/Cache/ChoiceLoaderTest.php index b2d0d2e6d700e..6134160046ddf 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/Cache/ChoiceLoaderTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/Cache/ChoiceLoaderTest.php @@ -26,9 +26,7 @@ public function testSameFormTypeUseCachedLoader() $choiceList = new ArrayChoiceList($choices); $type = new FormType(); - $decorated = new CallbackChoiceLoader(static function () use ($choices) { - return $choices; - }); + $decorated = new CallbackChoiceLoader(static fn () => $choices); $loader1 = new ChoiceLoader($type, $decorated); $loader2 = new ChoiceLoader($type, new ArrayChoiceLoader()); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php index 4f5c4eb0e342f..48aa3677a5966 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php @@ -146,9 +146,7 @@ public function testCreateFromChoicesSameFilterClosure() $filter = function () {}; $list1 = $this->factory->createListFromChoices($choices, null, $filter); $list2 = $this->factory->createListFromChoices($choices, null, $filter); - $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static function () use ($choices) { - return $choices; - }), $filter), null); + $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static fn () => $choices), $filter), null); $this->assertNotSame($list1, $list2); $this->assertEquals($lazyChoiceList, $list1); @@ -162,9 +160,7 @@ public function testCreateFromChoicesSameFilterClosureUseCache() $filterCallback = function () {}; $list1 = $this->factory->createListFromChoices($choices, null, ChoiceList::filter($formType, $filterCallback)); $list2 = $this->factory->createListFromChoices($choices, null, ChoiceList::filter($formType, function () {})); - $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static function () use ($choices) { - return $choices; - }), function () {}), null); + $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static fn () => $choices), function () {}), null); $this->assertSame($list1, $list2); $this->assertEquals($lazyChoiceList, $list1); @@ -178,9 +174,7 @@ public function testCreateFromChoicesDifferentFilterClosure() $closure2 = function () {}; $list1 = $this->factory->createListFromChoices($choices, null, $closure1); $list2 = $this->factory->createListFromChoices($choices, null, $closure2); - $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static function () use ($choices) { - return $choices; - }), function () {}), null); + $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static fn () => $choices), function () {}), null); $this->assertNotSame($list1, $list2); $this->assertEquals($lazyChoiceList, $list1); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index 054739c8ccd80..35526a98f671b 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -151,7 +151,7 @@ public function testCreateFromChoicesFlatValuesAsClosure() { $list = $this->factory->createListFromChoices( ['A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4], - function ($object) { return $object->value; } + fn ($object) => $object->value ); $this->assertObjectListWithCustomValues($list); @@ -199,7 +199,7 @@ public function testCreateFromChoicesGroupedValuesAsClosure() 'Group 1' => ['A' => $this->obj1, 'B' => $this->obj2], 'Group 2' => ['C' => $this->obj3, 'D' => $this->obj4], ], - function ($object) { return $object->value; } + fn ($object) => $object->value ); $this->assertObjectListWithCustomValues($list); @@ -210,9 +210,7 @@ public function testCreateFromFilteredChoices() $list = $this->factory->createListFromChoices( ['A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4, 'E' => $this->obj5, 'F' => $this->obj6], null, - function ($choice) { - return $choice !== $this->obj5 && $choice !== $this->obj6; - } + fn ($choice) => $choice !== $this->obj5 && $choice !== $this->obj6 ); $this->assertObjectListWithGeneratedValues($list); @@ -228,9 +226,7 @@ public function testCreateFromChoicesGroupedAndFiltered() 'Group 4' => [/* empty group should be filtered */], ], null, - function ($choice) { - return $choice !== $this->obj5 && $choice !== $this->obj6; - } + fn ($choice) => $choice !== $this->obj5 && $choice !== $this->obj6 ); $this->assertObjectListWithGeneratedValues($list); @@ -246,9 +242,7 @@ public function testCreateFromChoicesGroupedAndFilteredTraversable() 'Group 4' => [/* empty group should be filtered */], ]), null, - function ($choice) { - return $choice !== $this->obj5 && $choice !== $this->obj6; - } + fn ($choice) => $choice !== $this->obj5 && $choice !== $this->obj6 ); $this->assertObjectListWithGeneratedValues($list); @@ -313,9 +307,7 @@ public function testCreateViewFlatPreferredChoicesSameOrder() [$this->obj2, $this->obj1, $this->obj4, $this->obj3] ); - $preferredLabels = array_map(static function (ChoiceView $view): string { - return $view->label; - }, $view->preferredChoices); + $preferredLabels = array_map(static fn (ChoiceView $view): string => $view->label, $view->preferredChoices); $this->assertSame( [ @@ -338,11 +330,7 @@ public function testCreateViewFlatPreferredChoiceGroupsSameOrder() $this->getGroup(...) ); - $preferredLabels = array_map(static function (ChoiceGroupView $groupView): array { - return array_map(static function (ChoiceView $view): string { - return $view->label; - }, $groupView->choices); - }, $view->preferredChoices); + $preferredLabels = array_map(static fn (ChoiceGroupView $groupView): array => array_map(static fn (ChoiceView $view): string => $view->label, $groupView->choices), $view->preferredChoices); $this->assertEquals( [ @@ -393,9 +381,7 @@ public function testCreateViewFlatPreferredChoicesAsClosure() $view = $this->factory->createView( $this->list, - function ($object) use ($obj2, $obj3) { - return $obj2 === $object || $obj3 === $object; - } + fn ($object) => $obj2 === $object || $obj3 === $object ); $this->assertFlatView($view); @@ -405,9 +391,7 @@ public function testCreateViewFlatPreferredChoicesClosureReceivesKey() { $view = $this->factory->createView( $this->list, - function ($object, $key) { - return 'B' === $key || 'C' === $key; - } + fn ($object, $key) => 'B' === $key || 'C' === $key ); $this->assertFlatView($view); @@ -417,9 +401,7 @@ public function testCreateViewFlatPreferredChoicesClosureReceivesValue() { $view = $this->factory->createView( $this->list, - function ($object, $key, $value) { - return '1' === $value || '2' === $value; - } + fn ($object, $key, $value) => '1' === $value || '2' === $value ); $this->assertFlatView($view); @@ -441,9 +423,7 @@ public function testCreateViewFlatLabelAsClosure() $view = $this->factory->createView( $this->list, [$this->obj2, $this->obj3], - function ($object) { - return $object->label; - } + fn ($object) => $object->label ); $this->assertFlatView($view); @@ -454,9 +434,7 @@ public function testCreateViewFlatLabelClosureReceivesKey() $view = $this->factory->createView( $this->list, [$this->obj2, $this->obj3], - function ($object, $key) { - return $key; - } + fn ($object, $key) => $key ); $this->assertFlatView($view); @@ -498,9 +476,7 @@ public function testCreateViewFlatIndexAsClosure() $this->list, [$this->obj2, $this->obj3], null, // label - function ($object) { - return $object->index; - } + fn ($object) => $object->index ); $this->assertFlatViewWithCustomIndices($view); @@ -622,9 +598,7 @@ public function testCreateViewFlatGroupByAsClosure() [$this->obj2, $this->obj3], null, // label null, // index - function ($object) use ($obj1, $obj2) { - return $obj1 === $object || $obj2 === $object ? 'Group 1' : 'Group 2'; - } + fn ($object) => $obj1 === $object || $obj2 === $object ? 'Group 1' : 'Group 2' ); $this->assertGroupedView($view); @@ -637,9 +611,7 @@ public function testCreateViewFlatGroupByClosureReceivesKey() [$this->obj2, $this->obj3], null, // label null, // index - function ($object, $key) { - return 'A' === $key || 'B' === $key ? 'Group 1' : 'Group 2'; - } + fn ($object, $key) => 'A' === $key || 'B' === $key ? 'Group 1' : 'Group 2' ); $this->assertGroupedView($view); @@ -652,9 +624,7 @@ public function testCreateViewFlatGroupByClosureReceivesValue() [$this->obj2, $this->obj3], null, // label null, // index - function ($object, $key, $value) { - return '0' === $value || '1' === $value ? 'Group 1' : 'Group 2'; - } + fn ($object, $key, $value) => '0' === $value || '1' === $value ? 'Group 1' : 'Group 2' ); $this->assertGroupedView($view); @@ -713,9 +683,7 @@ public function testCreateViewFlatAttrAsClosure() null, // label null, // index null, // group - function ($object) { - return $object->attr; - } + fn ($object) => $object->attr ); $this->assertFlatViewWithAttr($view); @@ -729,12 +697,10 @@ public function testCreateViewFlatAttrClosureReceivesKey() null, // label null, // index null, // group - function ($object, $key) { - return match ($key) { - 'B' => ['attr1' => 'value1'], - 'C' => ['attr2' => 'value2'], - default => [], - }; + fn ($object, $key) => match ($key) { + 'B' => ['attr1' => 'value1'], + 'C' => ['attr2' => 'value2'], + default => [], } ); @@ -749,12 +715,10 @@ public function testCreateViewFlatAttrClosureReceivesValue() null, // label null, // index null, // group - function ($object, $key, $value) { - return match ($value) { - '1' => ['attr1' => 'value1'], - '2' => ['attr2' => 'value2'], - default => [], - }; + fn ($object, $key, $value) => match ($value) { + '1' => ['attr1' => 'value1'], + '2' => ['attr2' => 'value2'], + default => [], } ); @@ -766,9 +730,7 @@ public function testPassTranslatableMessageAsLabelDoesntCastItToString() $view = $this->factory->createView( $this->list, [$this->obj1], - static function ($choice, $key, $value) { - return new TranslatableMessage('my_message', ['param1' => 'value1']); - } + static fn ($choice, $key, $value) => new TranslatableMessage('my_message', ['param1' => 'value1']) ); $this->assertInstanceOf(TranslatableMessage::class, $view->choices[0]->label); @@ -788,9 +750,7 @@ public function trans(TranslatorInterface $translator, string $locale = null): s $view = $this->factory->createView( $this->list, [$this->obj1], - static function () use ($message) { - return $message; - } + static fn () => $message ); $this->assertSame($message, $view->choices[0]->label); @@ -852,9 +812,7 @@ public function testCreateViewFlatlabelTranslationParametersAsClosure() null, // index null, // group null, // attr - function ($object) { - return $object->labelTranslationParameters; - } + fn ($object) => $object->labelTranslationParameters ); $this->assertFlatViewWithlabelTranslationParameters($view); @@ -869,11 +827,9 @@ public function testCreateViewFlatlabelTranslationParametersClosureReceivesKey() null, // index null, // group null, // attr - function ($object, $key) { - return match ($key) { - 'D' => ['%placeholder1%' => 'value1'], - default => [], - }; + fn ($object, $key) => match ($key) { + 'D' => ['%placeholder1%' => 'value1'], + default => [], } ); @@ -889,11 +845,9 @@ public function testCreateViewFlatlabelTranslationParametersClosureReceivesValue null, // index null, // group null, // attr - function ($object, $key, $value) { - return match ($value) { - '3' => ['%placeholder1%' => 'value1'], - default => [], - }; + fn ($object, $key, $value) => match ($value) { + '3' => ['%placeholder1%' => 'value1'], + default => [], } ); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php index 94d41cf9e740f..501f4377ad559 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php @@ -108,9 +108,7 @@ public function testGetChoicesForValuesUsesLoadedList() 'b' => 'bar', 'c' => 'baz', ]; - $list = new LazyChoiceList(new ArrayChoiceLoader($choices), function ($choice) use ($choices) { - return array_search($choice, $choices); - }); + $list = new LazyChoiceList(new ArrayChoiceLoader($choices), fn ($choice) => array_search($choice, $choices)); // load choice list $list->getChoices(); @@ -126,9 +124,7 @@ public function testGetValuesForChoicesUsesLoadedList() 'b' => 'bar', 'c' => 'baz', ]; - $list = new LazyChoiceList(new ArrayChoiceLoader($choices), function ($choice) use ($choices) { - return array_search($choice, $choices); - }); + $list = new LazyChoiceList(new ArrayChoiceLoader($choices), fn ($choice) => array_search($choice, $choices)); // load choice list $list->getChoices(); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/CallbackChoiceLoaderTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/CallbackChoiceLoaderTest.php index 69eb787a23dfa..e3e813b377c6d 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/CallbackChoiceLoaderTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/CallbackChoiceLoaderTest.php @@ -48,12 +48,8 @@ class CallbackChoiceLoaderTest extends TestCase public static function setUpBeforeClass(): void { - self::$loader = new CallbackChoiceLoader(function () { - return self::$choices; - }); - self::$value = function ($choice) { - return $choice->value ?? null; - }; + self::$loader = new CallbackChoiceLoader(fn () => self::$choices); + self::$value = fn ($choice) => $choice->value ?? null; self::$choices = [ (object) ['value' => 'choice_one'], (object) ['value' => 'choice_two'], diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/FilterChoiceLoaderDecoratorTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/FilterChoiceLoaderDecoratorTest.php index 1f91a47275a33..5a41e5aff3415 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/FilterChoiceLoaderDecoratorTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/FilterChoiceLoaderDecoratorTest.php @@ -20,9 +20,7 @@ class FilterChoiceLoaderDecoratorTest extends TestCase { public function testLoadChoiceList() { - $filter = function ($choice) { - return 0 === $choice % 2; - }; + $filter = fn ($choice) => 0 === $choice % 2; $loader = new FilterChoiceLoaderDecorator(new ArrayChoiceLoader(range(1, 4)), $filter); @@ -31,9 +29,7 @@ public function testLoadChoiceList() public function testLoadChoiceListWithGroupedChoices() { - $filter = function ($choice) { - return $choice < 9 && 0 === $choice % 2; - }; + $filter = fn ($choice) => $choice < 9 && 0 === $choice % 2; $loader = new FilterChoiceLoaderDecorator(new ArrayChoiceLoader(['units' => range(1, 9), 'tens' => range(10, 90, 10)]), $filter); @@ -49,9 +45,7 @@ public function testLoadChoiceListWithGroupedChoices() public function testLoadChoiceListMixedWithGroupedAndNonGroupedChoices() { - $filter = function ($choice) { - return 0 === $choice % 2; - }; + $filter = fn ($choice) => 0 === $choice % 2; $choices = array_merge(range(1, 9), ['grouped' => range(10, 40, 5)]); $loader = new FilterChoiceLoaderDecorator(new ArrayChoiceLoader($choices), $filter); @@ -74,9 +68,7 @@ public function testLoadValuesForChoices() { $evenValues = [1 => '2', 3 => '4']; - $filter = function ($choice) { - return 0 === $choice % 2; - }; + $filter = fn ($choice) => 0 === $choice % 2; $loader = new FilterChoiceLoaderDecorator(new ArrayChoiceLoader([range(1, 4)]), $filter); @@ -88,9 +80,7 @@ public function testLoadChoicesForValues() $evenChoices = [1 => 2, 3 => 4]; $values = array_map('strval', range(1, 4)); - $filter = function ($choice) { - return 0 === $choice % 2; - }; + $filter = fn ($choice) => 0 === $choice % 2; $loader = new FilterChoiceLoaderDecorator(new ArrayChoiceLoader(range(1, 4)), $filter); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php index e2827b0d913be..0aed92fb5e901 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php @@ -49,12 +49,8 @@ class IntlCallbackChoiceLoaderTest extends TestCase public static function setUpBeforeClass(): void { - self::$loader = new IntlCallbackChoiceLoader(function () { - return self::$choices; - }); - self::$value = function ($choice) { - return $choice->value ?? null; - }; + self::$loader = new IntlCallbackChoiceLoader(fn () => self::$choices); + self::$value = fn ($choice) => $choice->value ?? null; self::$choices = [ (object) ['value' => 'choice_one'], (object) ['value' => 'choice_two'], diff --git a/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php index f8ce76316ff1e..9e3847d9ac646 100644 --- a/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php @@ -263,7 +263,7 @@ private static function getCoreTypes(): array $coreExtension = new CoreExtension(); $loadTypesRefMethod = (new \ReflectionObject($coreExtension))->getMethod('loadTypes'); $coreTypes = $loadTypesRefMethod->invoke($coreExtension); - $coreTypes = array_map(function (FormTypeInterface $type) { return $type::class; }, $coreTypes); + $coreTypes = array_map(fn (FormTypeInterface $type) => $type::class, $coreTypes); sort($coreTypes); return $coreTypes; @@ -290,15 +290,11 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setDefault('empty_data', function (Options $options) { $foo = $options['foo']; - return function (FormInterface $form) use ($foo) { - return $form->getConfig()->getCompound() ? [$foo] : $foo; - }; + return fn (FormInterface $form) => $form->getConfig()->getCompound() ? [$foo] : $foo; }); $resolver->setAllowedTypes('foo', 'string'); $resolver->setAllowedValues('foo', ['bar', 'baz']); - $resolver->setNormalizer('foo', function (Options $options, $value) { - return (string) $value; - }); + $resolver->setNormalizer('foo', fn (Options $options, $value) => (string) $value); $resolver->setInfo('foo', 'Info'); } } diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index 833dcc7616ad2..52efb607b2033 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -955,10 +955,8 @@ public function testCreateViewWithChildren() $this->form->add($field1); $this->form->add($field2); - $assertChildViewsEqual = function (array $childViews) { - return function (FormView $view) use ($childViews) { - $this->assertSame($childViews, $view->children); - }; + $assertChildViewsEqual = fn (array $childViews) => function (FormView $view) use ($childViews) { + $this->assertSame($childViews, $view->children); }; // First create the view diff --git a/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php b/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php index bc870494e6461..15532929419ae 100644 --- a/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php +++ b/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php @@ -167,14 +167,10 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setDefault('empty_data', function (Options $options, $value) { $foo = $options['foo']; - return function (FormInterface $form) use ($foo) { - return $form->getConfig()->getCompound() ? [$foo] : $foo; - }; + return fn (FormInterface $form) => $form->getConfig()->getCompound() ? [$foo] : $foo; }); $resolver->setAllowedTypes('foo', 'string'); $resolver->setAllowedValues('foo', ['bar', 'baz']); - $resolver->setNormalizer('foo', function (Options $options, $value) { - return (string) $value; - }); + $resolver->setNormalizer('foo', fn (Options $options, $value) => (string) $value); } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php index b39572ed808a5..a125078fd69b6 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php @@ -359,9 +359,7 @@ public function testMapDataToFormsUsingGetCallbackOption() $person = new DummyPerson($initialName); $config = new FormConfigBuilder('name', null, $this->dispatcher, [ - 'getter' => static function (DummyPerson $person) { - return $person->myName(); - }, + 'getter' => static fn (DummyPerson $person) => $person->myName(), ]); $form = new Form($config); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php index 9bcb22efe0473..d42d4d8899585 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php @@ -285,9 +285,7 @@ public function testOnSubmitDeleteEmptyCompoundEntriesIfAllowDelete() $this->form->get($child)->submit($dat); } $event = new FormEvent($this->form, $data); - $callback = function ($data) { - return null === $data['name']; - }; + $callback = fn ($data) => null === $data['name']; $listener = new ResizeFormListener('text', [], false, true, $callback); $listener->onSubmit($event); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php index 93ca921b7c650..95ef611b1c2b7 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ 10000 CheckboxTypeTest.php @@ -146,12 +146,8 @@ public function testCustomModelTransformer($data, $checked) { // present a binary status field as a checkbox $transformer = new CallbackTransformer( - function ($value) { - return 'checked' == $value; - }, - function ($value) { - return $value ? 'checked' : 'unchecked'; - } + fn ($value) => 'checked' == $value, + fn ($value) => $value ? 'checked' : 'unchecked' ); $form = $this->factory->createBuilder(static::TESTED_TYPE) 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 8109a9c60079d..8bf3dc23594db 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -531,9 +531,7 @@ public function testSubmitSingleNonExpandedEmptyExplicitEmptyChoice() 'choices' => [ 'Empty' => 'EMPTY_CHOICE', ], - 'choice_value' => function () { - return ''; - }, + 'choice_value' => fn () => '', ]); $form->submit(''); @@ -2208,9 +2206,7 @@ public function testFilteredChoices() { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'choices' => $this->choices, - 'choice_filter' => function ($choice) { - return \in_array($choice, range('a', 'c'), true); - }, + 'choice_filter' => fn ($choice) => \in_array($choice, range('a', 'c'), true), ]); $this->assertEquals([ @@ -2224,9 +2220,7 @@ public function testFilteredGroupedChoices() { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'choices' => $this->groupedChoices, - 'choice_filter' => function ($choice) { - return \in_array($choice, range('a', 'c'), true); - }, + 'choice_filter' => fn ($choice) => \in_array($choice, range('a', 'c'), true), ]); $this->assertEquals(['Symfony' => new ChoiceGroupView('Symfony', [ @@ -2239,12 +2233,8 @@ public function testFilteredGroupedChoices() public function testFilteredChoiceLoader() { $form = $this->factory->create(static::TESTED_TYPE, null, [ - 'choice_loader' => new CallbackChoiceLoader(function () { - return $this->choices; - }), - 'choice_filter' => function ($choice) { - return \in_array($choice, range('a', 'c'), true); - }, + 'choice_loader' => new CallbackChoiceLoader(fn () => $this->choices), + 'choice_filter' => fn ($choice) => \in_array($choice, range('a', 'c'), true), ]); $this->assertEquals([ @@ -2256,9 +2246,7 @@ public function testFilteredChoiceLoader() public function testWithSameLoaderAndDifferentChoiceValueCallbacks() { - $choiceLoader = new CallbackChoiceLoader(function () { - return [1, 2, 3]; - }); + $choiceLoader = new CallbackChoiceLoader(fn () => [1, 2, 3]); $view = $this->factory->create(FormTypeTest::TESTED_TYPE) ->add('choice_one', self::TESTED_TYPE, [ @@ -2266,9 +2254,7 @@ public function testWithSameLoaderAndDifferentChoiceValueCallbacks() ]) ->add('choice_two', self::TESTED_TYPE, [ 'choice_loader' => $choiceLoader, - 'choice_value' => function ($choice) { - return $choice ? (string) $choice * 10 : ''; - }, + 'choice_value' => fn ($choice) => $choice ? (string) $choice * 10 : '', ]) ->createView() ; diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php index 9e144100590ac..490e84604aa15 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php @@ -31,9 +31,7 @@ protected function getExtensions() { $translator = $this->createMock(TranslatorInterface::class); $translator->expects($this->any())->method('trans') - ->willReturnCallback(function ($key, $params) { - return strtr(sprintf('Translation of: %s', $key), $params); - } + ->willReturnCallback(fn ($key, $params) => strtr(sprintf('Translation of: %s', $key), $params) ); return array_merge(parent::getExtensions(), [new CoreExtension(null, null, $translator)]); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php index 129c2c97b9a34..9df4eede1e482 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php @@ -120,9 +120,7 @@ public function testResizedDownWithDeleteEmptyCallable() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'entry_type' => AuthorType::class, 'allow_delete' => true, - 'delete_empty' => function (Author $obj = null) { - return null === $obj || empty($obj->firstName); - }, + 'delete_empty' => fn (Author $obj = null) => null === $obj || empty($obj->firstName), ]); $form->setData([new Author('Bob'), new Author('Alice')]); @@ -143,9 +141,7 @@ public function testResizedDownIfSubmittedWithCompoundEmptyDataDeleteEmptyAndNoD 'entry_options' => ['data_class' => null], 'allow_add' => true, 'allow_delete' => true, - 'delete_empty' => function ($author) { - return empty($author['firstName']); - }, + 'delete_empty' => fn ($author) => empty($author['firstName']), ]); $form->setData([['firstName' => 'first', 'lastName' => 'last']]); $form->submit([ 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 71edd6afc7d61..12b6939a68b02 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -709,9 +709,7 @@ 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'; - }; + $lazyEmptyData = static fn (FormInterface $form) => $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], 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 74e0a8d35524f..62a12289cb301 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -1057,9 +1057,7 @@ 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'; - }; + $lazyEmptyData = static fn (FormInterface $form) => $form->getConfig()->getCompound() ? ['year' => '2018', 'month' => '11', 'day' => '11'] : '2018-11-11'; return [ 'Simple field' => ['single_text', '2018-11-11', $expectedData], diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php index 3701b653f855e..b4bca1513083d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php @@ -439,9 +439,8 @@ public function testSubformCallsSettersIfReferenceIsScalar() $builder->add('referenceCopy', static::TESTED_TYPE); $builder->get('referenceCopy')->addViewTransformer(new CallbackTransformer( function () {}, - function ($value) { // reverseTransform - return 'foobar'; - } + fn ($value) => // reverseTransform +'foobar' )); $form = $builder->getForm(); @@ -464,9 +463,8 @@ public function testSubformAlwaysInsertsIntoArrays() $builder->add('referenceCopy', static::TESTED_TYPE); $builder->get('referenceCopy')->addViewTransformer(new CallbackTransformer( function () {}, - function ($value) use ($ref2) { // reverseTransform - return $ref2; - } + fn ($value) => // reverseTransform +$ref2 )); $form = $builder->getForm(); 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 08284dbbf00e7..991d9c23fe865 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php @@ -1113,9 +1113,7 @@ 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'; - }; + $lazyEmptyData = static fn (FormInterface $form) => $form->getConfig()->getCompound() ? ['hour' => '21', 'minute' => '23'] : '21:23'; return [ 'Simple field' => ['single_text', '21:23', $expectedData], diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php index 39009b598c530..fd9870fa6d50e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php @@ -335,9 +335,7 @@ public function testSerializeWithFormAddedMultipleTimes() $form1View = new FormView(); $form2View = new FormView(); $child1View = new FormView(); - $child1View->vars['is_selected'] = function ($choice, array $values) { - return \in_array($choice, $values, true); - }; + $child1View->vars['is_selected'] = fn ($choice, array $values) => \in_array($choice, $values, true); $form1->add($child1); $form2->add($child1); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index c614a1ac181f4..7d9061c882808 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -272,7 +272,7 @@ public function testDontValidateIfNotSynchronized() ]) ->setData($object) ->addViewTransformer(new CallbackTransformer( - function ($data) { return $data; }, + fn ($data) => $data, function () { throw new TransformationFailedException(); } )) ->getForm(); @@ -309,7 +309,7 @@ public function testAddInvalidErrorEvenIfNoValidationGroups() ]) ->setData($object) ->addViewTransformer(new CallbackTransformer( - function ($data) { return $data; }, + fn ($data) => $data, function () { throw new TransformationFailedException(); } )) ->getForm(); @@ -344,7 +344,7 @@ public function testDontValidateConstraintsIfNotSynchronized() $form = $this->getBuilder('name', '\stdClass', $options) ->setData($object) ->addViewTransformer(new CallbackTransformer( - function ($data) { return $data; }, + fn ($data) => $data, function () { throw new TransformationFailedException(); } )) ->getForm(); @@ -375,7 +375,7 @@ public function testTransformationFailedExceptionInvalidMessageIsUsed() ]) ->setData($object) ->addViewTransformer(new CallbackTransformer( - function ($data) { return $data; }, + fn ($data) => $data, function () { $failure = new TransformationFailedException(); $failure->setInvalidMessage('safe message to be used', ['{{ bar }}' => 'bar']); @@ -451,9 +451,7 @@ public function testDontExecuteFunctionNames() public function testHandleClosureValidationGroups() { $object = new \stdClass(); - $options = ['validation_groups' => function (FormInterface $form) { - return ['group1', 'group2']; - }]; + $options = ['validation_groups' => fn (FormInterface $form) => ['group1', 'group2']]; $form = $this->getCompoundForm($object, $options); $form->submit([]); @@ -565,9 +563,7 @@ public function testUseInheritedClosureValidationGroup() $object = new \stdClass(); $parentOptions = [ - 'validation_groups' => function () { - return ['group1', 'group2']; - }, + 'validation_groups' => fn () => ['group1', 'group2'], ]; $parent = $this->getBuilder('parent', null, $parentOptions) ->setCompound(true) diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php index 34c911f52a9fa..0533883f701f2 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php @@ -26,11 +26,7 @@ public function testPostMaxSizeTranslation() $resolver = new OptionsResolver(); $resolver->setDefault('post_max_size_message', 'old max {{ max }}!'); - $resolver->setDefault('upload_max_size_message', function (Options $options) { - return function () use ($options) { - return $options['post_max_size_message']; - }; - }); + $resolver->setDefault('upload_max_size_message', fn (Options $options) => fn () => $options['post_max_size_message']); $extension->configureOptions($resolver); $options = $resolver->resolve(); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php index 08b8caaedd5f5..f0a9124c6f568 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php @@ -91,7 +91,7 @@ protected function getForm($name = 'name', $propertyPath = null, $dataClass = nu if (!$synchronized) { $config->addViewTransformer(new CallbackTransformer( - function ($normData) { return $normData; }, + fn ($normData) => $normData, function () { throw new TransformationFailedException(); } )); } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/ArrayChoiceLoader.php b/src/Symfony/Component/Form/Tests/Fixtures/ArrayChoiceLoader.php index 7224d0e93639e..8c5a3a4733e6c 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/ArrayChoiceLoader.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/ArrayChoiceLoader.php @@ -8,8 +8,6 @@ class ArrayChoiceLoader extends CallbackChoiceLoader { public function __construct(array $choices = []) { - parent::__construct(static function () use ($choices): array { - return $choices; - }); + parent::__construct(static fn (): array => $choices); } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/ChoiceSubType.php b/src/Symfony/Component/Form/Tests/Fixtures/ChoiceSubType.php index d67f0b96ace50..7399dfe36bdba 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/ChoiceSubType.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/ChoiceSubType.php @@ -23,12 +23,10 @@ class ChoiceSubType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(['expanded' => true]); - $resolver->setNormalizer('choices', function () { - return [ - 'attr1' => 'Attribute 1', - 'attr2' => 'Attribute 2', - ]; - }); + $resolver->setNormalizer('choices', fn () => [ + 'attr1' => 'Attribute 1', + 'attr2' => 'Attribute 2', + ]); } public function getParent(): ?string diff --git a/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php index b04eb61721c41..ccd5b3f4890f5 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php @@ -21,12 +21,10 @@ class LazyChoiceTypeExtension extends AbstractTypeExtension public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefault('choice_loader', ChoiceList::lazy($this, function () { - return [ - 'Lazy A' => 'lazy_a', - 'Lazy B' => 'lazy_b', - ]; - })); + $resolver->setDefault('choice_loader', ChoiceList::lazy($this, fn () => [ + 'Lazy A' => 'lazy_a', + 'Lazy B' => 'lazy_b', + ])); } public static function getExtendedTypes(): iterable diff --git a/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php b/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php index 1b83bfc3bf452..00e6c687c91c5 100644 --- a/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php +++ b/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php @@ -46,7 +46,7 @@ public function testTranslationFileIsValidWithoutEntityLoader($filePath) public function provideTranslationFiles() { return array_map( - function ($filePath) { return (array) $filePath; }, + fn ($filePath) => (array) $filePath, glob(\dirname(__DIR__, 2).'/Resources/translations/*.xlf') ); } diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 3d8b0b20d83f1..19ae3b507455c 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -1112,12 +1112,12 @@ public function testIsEmptyCallback() { $config = new FormConfigBuilder('foo', null, new EventDispatcher()); - $config->setIsEmptyCallback(function ($modelData): bool { return 'ccc' === $modelData; }); + $config->setIsEmptyCallback(fn ($modelData): bool => 'ccc' === $modelData); $form = new Form($config); $form->setData('ccc'); $this->assertTrue($form->isEmpty()); - $config->setIsEmptyCallback(function (): bool { return false; }); + $config->setIsEmptyCallback(fn (): bool => false); $form = new Form($config); $form->setData(null); $this->assertFalse($form->isEmpty()); diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index c1e3dd9fa6e48..460398a983f60 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -382,14 +382,10 @@ private static function createRedirectResolver(array $options, string $host, int if (0 < $options['max_redirects']) { $redirectHeaders['host'] = $host; $redirectHeaders['port'] = $port; - $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { - return 0 !== stripos($h, 'Host:'); - }); + $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static fn ($h) => 0 !== stripos($h, 'Host:')); if (isset($options['normalized_headers']['authorization'][0]) || isset($options['normalized_headers']['cookie'][0])) { - $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { - return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:'); - }); + $redirectHeaders['no_auth'] = array_filter($options['headers'], static fn ($h) => 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:')); } } @@ -401,9 +397,7 @@ private static function createRedirectResolver(array $options, string $host, int } if ($noContent && $redirectHeaders) { - $filterContentHeaders = static function ($h) { - return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); - }; + $filterContentHeaders = static fn ($h) => 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); } @@ -431,9 +425,7 @@ private static function createRedirectResolver(array $options, string $host, int private function findConstantName(int $opt): ?string { - $constants = array_filter(get_defined_constants(), static function ($v, $k) use ($opt) { - return $v === $opt && 'C' === $k[0] && (str_starts_with($k, 'CURLOPT_') || str_starts_with($k, 'CURLINFO_')); - }, \ARRAY_FILTER_USE_BOTH); + $constants = array_filter(get_defined_constants(), static fn ($v, $k) => $v === $opt && 'C' === $k[0] && (str_starts_with($k, 'CURLOPT_') || str_starts_with($k, 'CURLINFO_')), \ARRAY_FILTER_USE_BOTH); return key($constants); } diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index d47158e52d99c..0997fe4dc996b 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -330,19 +330,17 @@ private static function normalizeBody($body) return $body; } - $generatorToCallable = static function (\Generator $body): \Closure { - return static function () use ($body) { - while ($body->valid()) { - $chunk = $body->current(); - $body->next(); - - if ('' !== $chunk) { - return $chunk; - } + $generatorToCallable = static fn (\Generator $body): \Closure => static function () use ($body) { + while ($body->valid()) { + $chunk = $body->current(); + $body->next(); + + if ('' !== $chunk) { + return $chunk; } + } - return ''; - }; + return ''; }; if ($body instanceof \Generator) { @@ -536,11 +534,11 @@ private static function parseUrl(string $url, array $query = [], array $allowedS if (str_contains($parts[$part], '%')) { // https://tools.ietf.org/html/rfc3986#section-2.3 - $parts[$part] = preg_replace_callback('/%(?:2[DE]|3[0-9]|[46][1-9A-F]|5F|[57][0-9A]|7E)++/i', function ($m) { return rawurldecode($m[0]); }, $parts[$part]); + $parts[$part] = preg_replace_callback('/%(?:2[DE]|3[0-9]|[46][1-9A-F]|5F|[57][0-9A]|7E)++/i', fn ($m) => rawurldecode($m[0]), $parts[$part]); } // https://tools.ietf.org/html/rfc3986#section-3.3 - $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()*+,;=:@%]++#", function ($m) { return rawurlencode($m[0]); }, $parts[$part]); + $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()*+,;=:@%]++#", fn ($m) => rawurlencode($m[0]), $parts[$part]); } return [ diff --git a/src/Symfony/Component/HttpClient/Internal/AmpClientState.php b/src/Symfony/Component/HttpClient/Internal/AmpClientState.php index 18a1722c38115..2360a7f87d0e5 100644 --- a/src/Symfony/Component/HttpClient/Internal/AmpClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/AmpClientState.php @@ -94,9 +94,7 @@ public function request(array $options, Request $request, CancellationToken $can } $request->addEventListener(new AmpListener($info, $options['peer_fingerprint']['pin-sha256'] ?? [], $onProgress, $handle)); - $request->setPushHandler(function ($request, $response) use ($options): Promise { - return $this->handlePush($request, $response, $options); - }); + $request->setPushHandler(fn ($request, $response): Promise => $this->handlePush($request, $response, $options)); ($request->hasHeader('content-length') ? new Success((int) $request->getHeader('content-length')) : $request->getBody()->getBodyLength()) ->onResolve(static function ($e, $bodySize) use (&$info) { diff --git a/src/Symfony/Component/HttpClient/Internal/CurlClientState.php b/src/Symfony/Component/HttpClient/Internal/CurlClientState.php index aaaae4b23a5ef..61fd5ee9b61a4 100644 --- a/src/Symfony/Component/HttpClient/Internal/CurlClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/CurlClientState.php @@ -74,9 +74,7 @@ public function __construct(int $maxHostConnections, int $maxPendingPushes) $multi->handlesActivity = &$this->handlesActivity; $multi->openHandles = &$this->openHandles; - curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes) { - return $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes); - }); + curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, static fn ($parent, $pushed, array $requestHeaders) => $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes)); } public function reset() diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index 734effbb40354..23acce28de0c1 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -223,7 +223,7 @@ public function request(string $method, string $url, array $options = []): Respo 'allow_self_signed' => (bool) $options['peer_fingerprint'], 'SNI_enabled' => true, 'disable_compression' => true, - ], static function ($v) { return null !== $v; }), + ], static fn ($v) => null !== $v), 'socket' => [ 'bindto' => $options['bindto'], 'tcp_nodelay' => true, @@ -342,14 +342,10 @@ private static function createRedirectResolver(array $options, string $host, str $redirectHeaders = []; if (0 < $maxRedirects = $options['max_redirects']) { $redirectHeaders = ['host' => $host, 'port' => $port]; - $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { - return 0 !== stripos($h, 'Host:'); - }); + $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static fn ($h) => 0 !== stripos($h, 'Host:')); if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) { - $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) { - return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:'); - }); + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static fn ($h) => 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:')); } } @@ -386,9 +382,7 @@ private static function createRedirectResolver(array $options, string $host, str if ('POST' === $options['method'] || 303 === $info['http_code']) { $info['http_method'] = $options['method'] = 'HEAD' === $options['method'] ? 'HEAD' : 'GET'; $options['content'] = ''; - $filterContentHeaders = static function ($h) { - return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); - }; + $filterContentHeaders = static fn ($h) => 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); $options['header'] = array_filter($options['header'], $filterContentHeaders); $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); diff --git a/src/Symfony/Component/HttpClient/Response/AmpResponse.php b/src/Symfony/Component/HttpClient/Response/AmpResponse.php index d46e4036d801c..1c143e5c83041 100644 --- a/src/Symfony/Component/HttpClient/Response/AmpResponse.php +++ b/src/Symfony/Component/HttpClient/Response/AmpResponse.php @@ -67,9 +67,7 @@ public function __construct(AmpClientState $multi, Request $request, array $opti $request->setHeader('Accept-Encoding', 'gzip'); } - $this->initializer = static function (self $response) { - return null !== $response->options; - }; + $this->initializer = static fn (self $response) => null !== $response->options; $info = &$this->info; $headers = &$this->headers; diff --git a/src/Symfony/Component/HttpClient/Response/HttplugPromise.php b/src/Symfony/Component/HttpClient/Response/HttplugPromise.php index deaea720da735..e9dc24041e5fa 100644 --- a/src/Symfony/Component/HttpClient/Response/HttplugPromise.php +++ b/src/Symfony/Component/HttpClient/Response/HttplugPromise.php @@ -68,8 +68,6 @@ private function wrapThenCallback(?callable $callback): ?callable return null; } - return static function ($value) use ($callback) { - return Create::promiseFor($callback($value)); - }; + return static fn ($value) => Create::promiseFor($callback($value)); } } diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php index 59e73ba1ba176..350e9c49f85fa 100644 --- a/src/Symfony/Component/HttpClient/Response/MockResponse.php +++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php @@ -121,9 +121,7 @@ public static function fromRequest(string $method, string $url, array $options, $response->requestOptions = $options; $response->id = ++self::$idSequence; $response->shouldBuffer = $options['buffer'] ?? true; - $response->initializer = static function (self $response) { - return \is_array($response->body[0] ?? null); - }; + $response->initializer = static fn (self $response) => \is_array($response->body[0] ?? null); $response->info['redirect_count'] = 0; $response->info['redirect_url'] = null; diff --git a/src/Symfony/Component/HttpClient/Response/NativeResponse.php b/src/Symfony/Component/HttpClient/Response/NativeResponse.php index a6b9dd989bebb..3d2b26dae112c 100644 --- a/src/Symfony/Component/HttpClient/Response/NativeResponse.php +++ b/src/Symfony/Component/HttpClient/Response/NativeResponse.php @@ -71,9 +71,7 @@ public function __construct(NativeClientState $multi, $context, string $url, arr $info['max_duration'] = $options['max_duration']; ++$multi->responseCount; - $this->initializer = static function (self $response) { - return null === $response->remaining; - }; + $this->initializer = static fn (self $response) => null === $response->remaining; $pauseExpiry = &$this->pauseExpiry; $info['pause_handler'] = static function (float $duration) use (&$pauseExpiry) { diff --git a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php index db3ce095d9df6..7d5d6464a81c0 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php @@ -251,11 +251,7 @@ function (\Exception $exception) use ($errorMessage, &$failureCallableCalled, $c $failureCallableCalled = true; // Ensure arbitrary levels of promises work. - return (new FulfilledPromise(null))->then(function () use ($client, $request) { - return (new GuzzleFulfilledPromise(null))->then(function () use ($client, $request) { - return $client->sendAsyncRequest($request); - }); - }); + return (new FulfilledPromise(null))->then(fn () => (new GuzzleFulfilledPromise(null))->then(fn () => $client->sendAsyncRequest($request))); } ) ; diff --git a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php index 495644211990e..c47428fb9a198 100644 --- a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php @@ -45,9 +45,7 @@ public function testMocking($factory, array $expectedResponses) public function mockingProvider(): iterable { yield 'callable' => [ - static function (string $method, string $url, array $options = []) { - return new MockResponse($method.': '.$url.' (body='.$options['body'].')'); - }, + static fn (string $method, string $url, array $options = []) => new MockResponse($method.': '.$url.' (body='.$options['body'].')'), [ 'POST: https://example.com/foo (body=payload)', 'POST: https://example.com/bar (body=payload)', @@ -56,12 +54,8 @@ static function (string $method, string $url, array $options = []) { yield 'array of callable' => [ [ - static function (string $method, string $url, array $options = []) { - return new MockResponse($method.': '.$url.' (body='.$options['body'].') [1]'); - }, - static function (string $method, string $url, array $options = []) { - return new MockResponse($method.': '.$url.' (body='.$options['body'].') [2]'); - }, + static fn (string $method, string $url, array $options = []) => new MockResponse($method.': '.$url.' (body='.$options['body'].') [1]'), + static fn (string $method, string $url, array $options = []) => new MockResponse($method.': '.$url.' (body='.$options['body'].') [2]'), ], [ 'POST: https://example.com/foo (body=payload) [1]', @@ -115,7 +109,7 @@ public function testValidResponseFactory($responseFactory) public function validResponseFactoryProvider() { return [ - [static function (): MockResponse { return new MockResponse(); }], + [static fn (): MockResponse => new MockResponse()], [new MockResponse()], [[new MockResponse()]], [new \ArrayIterator([new MockResponse()])], @@ -142,12 +136,8 @@ public function transportExceptionProvider(): iterable { yield 'array of callable' => [ [ - static function (string $method, string $url, array $options = []) { - return new MockResponse(); - }, - static function (string $method, string $url, array $options = []) { - return new MockResponse(); - }, + static fn (string $method, string $url, array $options = []) => new MockResponse(), + static fn (string $method, string $url, array $options = []) => new MockResponse(), ], ]; @@ -183,7 +173,7 @@ public function invalidResponseFactoryProvider() { return [ [static function (): \Generator { yield new MockResponse(); }, 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "Generator" given.'], - [static function (): array { return [new MockResponse()]; }, 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "array" given.'], + [static fn (): array => [new MockResponse()], 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "array" given.'], [(static function (): \Generator { yield 'ccc'; })(), 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "string" given.'], ]; } diff --git a/src/Symfony/Component/HttpClient/Tests/Response/HttplugPromiseTest.php b/src/Symfony/Component/HttpClient/Tests/Response/HttplugPromiseTest.php index d781d4925b17b..9fb3ad2fc9631 100644 --- a/src/Symfony/Component/HttpClient/Tests/Response/HttplugPromiseTest.php +++ b/src/Symfony/Component/HttpClient/Tests/Response/HttplugPromiseTest.php @@ -29,7 +29,7 @@ public function testComplexNesting() $promise1 = $mkPromise('result'); $promise2 = $promise1->then($mkPromise); - $promise3 = $promise2->then(function ($result) { return $result; }); + $promise3 = $promise2->then(fn ($result) => $result); $this->assertSame('result', $promise3->wait()); } diff --git a/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php index 5f20e1989dfa1..cf437a653bd76 100755 --- a/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php @@ -123,9 +123,7 @@ public function testToArrayChecksStatusCodeBeforeDecoding() { $this->expectException(ClientExceptionInterface::class); - $sut = new TraceableHttpClient(new MockHttpClient($responseFactory = function (): MockResponse { - return new MockResponse('Errored.', ['http_code' => 400]); - })); + $sut = new TraceableHttpClient(new MockHttpClient($responseFactory = fn (): MockResponse => new MockResponse('Errored.', ['http_code' => 400]))); $response = $sut->request('GET', 'https://example.com/foo/bar'); $response->toArray(); diff --git a/src/Symfony/Component/HttpFoundation/AcceptHeader.php b/src/Symfony/Component/HttpFoundation/AcceptHeader.php index 180e9604c7fad..5edf5f5f18251 100644 --- a/src/Symfony/Component/HttpFoundation/AcceptHeader.php +++ b/src/Symfony/Component/HttpFoundation/AcceptHeader.php @@ -115,9 +115,7 @@ public function all(): array */ public function filter(string $pattern): self { - return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { - return preg_match($pattern, $item->getValue()); - })); + return new self(array_filter($this->items, fn (AcceptHeaderItem $item) => preg_match($pattern, $item->getValue()))); } /** diff --git a/src/Symfony/Component/HttpFoundation/FileBag.php b/src/Symfony/Component/HttpFoundation/FileBag.php index 7ed39408fd5af..3ccc45d1d5431 100644 --- a/src/Symfony/Component/HttpFoundation/FileBag.php +++ b/src/Symfony/Component/HttpFoundation/FileBag.php @@ -75,7 +75,7 @@ protected function convertFileInformation(array|UploadedFile $file): array|Uploa $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error'], false); } } else { - $file = array_map(function ($v) { return $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v; }, $file); + $file = array_map(fn ($v) => $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v, $file); if (array_keys($keys) === $keys) { $file = array_filter($file); } diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 3cf8a9954007d..56d74874c1319 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -605,9 +605,7 @@ public static function getTrustedHeaderSet(): int */ public static function setTrustedHosts(array $hostPatterns) { - self::$trustedHostPatterns = array_map(function ($hostPattern) { - return sprintf('{%s}i', $hostPattern); - }, $hostPatterns); + self::$trustedHostPatterns = array_map(fn ($hostPattern) => sprintf('{%s}i', $hostPattern), $hostPatterns); // we need to reset trusted hosts on trusted host patterns change self::$trustedHosts = []; } diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher.php index c2addd36e8ad1..28cdd20c55f32 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher.php @@ -120,9 +120,7 @@ public function matchIps(string|array|null $ips) { $ips = null !== $ips ? (array) $ips : []; - $this->ips = array_reduce($ips, static function (array $ips, string $ip) { - return array_merge($ips, preg_split('/\s*,\s*/', $ip)); - }, []); + $this->ips = array_reduce($ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []); } /** diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/IpsRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/IpsRequestMatcher.php index 2ddff038df769..333612e2f29b2 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher/IpsRequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/IpsRequestMatcher.php @@ -30,9 +30,7 @@ class IpsRequestMatcher implements RequestMatcherInterface */ public function __construct(array|string $ips) { - $this->ips = array_reduce((array) $ips, static function (array $ips, string $ip) { - return array_merge($ips, preg_split('/\s*,\s*/', $ip)); - }, []); + $this->ips = array_reduce((array) $ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []); } public function matches(Request $request): bool diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/MethodRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/MethodRequestMatcher.php index c7a915980c239..b37f6e3c87f96 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher/MethodRequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/MethodRequestMatcher.php @@ -32,9 +32,7 @@ class MethodRequestMatcher implements RequestMatcherInterface */ public function __construct(array|string $methods) { - $this->methods = array_reduce(array_map('strtoupper', (array) $methods), static function (array $methods, string $method) { - return array_merge($methods, preg_split('/\s*,\s*/', $method)); - }, []); + $this->methods = array_reduce(array_map('strtoupper', (array) $methods), static fn (array $methods, string $method) => array_merge($methods, preg_split('/\s*,\s*/', $method)), []); } public function matches(Request $request): bool diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/SchemeRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/SchemeRequestMatcher.php index 4f5eabc2c5ba1..9c9cd58b983cc 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher/SchemeRequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/SchemeRequestMatcher.php @@ -32,9 +32,7 @@ class SchemeRequestMatcher implements RequestMatcherInterface */ public function __construct(array|string $schemes) { - $this->schemes = array_reduce(array_map('strtolower', (array) $schemes), static function (array $schemes, string $scheme) { - return array_merge($schemes, preg_split('/\s*,\s*/', $scheme)); - }, []); + $this->schemes = array_reduce(array_map('strtolower', (array) $schemes), static fn (array $schemes, string $scheme) => array_merge($schemes, preg_split('/\s*,\s*/', $scheme)), []); } public function matches(Request $request): bool diff --git a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php index b3d375e4c37f9..417efc77a6688 100644 --- a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php +++ b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php @@ -69,9 +69,7 @@ protected function getCookie(Response $response): ?Cookie { $cookies = $response->headers->getCookies(); - $filteredCookies = array_filter($cookies, function (Cookie $cookie) { - return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; - }); + $filteredCookies = array_filter($cookies, fn (Cookie $cookie) => $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain); return reset($filteredCookies) ?: null; } diff --git a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php index 9b15aeae83785..73393d386fbce 100644 --- a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php +++ b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php @@ -61,9 +61,7 @@ private function getCookie(Response $response): ?Cookie { $cookies = $response->headers->getCookies(); - $filteredCookies = array_filter($cookies, function (Cookie $cookie) { - return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; - }); + $filteredCookies = array_filter($cookies, fn (Cookie $cookie) => $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain); return reset($filteredCookies) ?: null; } diff --git a/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php index 696318e91ea98..8d758916cb2c9 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php @@ -64,9 +64,7 @@ public function testFilterCallback() public function testFilterClosure() { $bag = new InputBag(['foo' => 'bar']); - $result = $bag->filter('foo', null, \FILTER_CALLBACK, ['options' => function ($value) { - return strtoupper($value); - }]); + $result = $bag->filter('foo', null, \FILTER_CALLBACK, ['options' => strtoupper(...)]); $this->assertSame('BAR', $result); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php index 1b60fb2418008..7a737e2f20fcc 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php @@ -188,9 +188,7 @@ public function testFilterCallback() public function testFilterClosure() { $bag = new ParameterBag(['foo' => 'bar']); - $result = $bag->filter('foo', null, \FILTER_CALLBACK, ['options' => function ($value) { - return strtoupper($value); - }]); + $result = $bag->filter('foo', null, \FILTER_CALLBACK, ['options' => strtoupper(...)]); $this->assertSame('BAR', $result); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/AttributesRequestMatcherTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/AttributesRequestMatcherTest.php index 9ca88765234f7..ce8cc7f20c96c 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/AttributesRequestMatcherTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/AttributesRequestMatcherTest.php @@ -26,9 +26,7 @@ public function test(string $key, string $regexp, bool $expected) $matcher = new AttributesRequestMatcher([$key => $regexp]); $request = Request::create('/admin/foo'); $request->attributes->set('foo', 'foo_bar'); - $request->attributes->set('_controller', function () { - return new Response('foo'); - }); + $request->attributes->set('_controller', fn () => new Response('foo')); $this->assertSame($expected, $matcher->matches($request)); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php index 0419d36932dee..a51df62340109 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php @@ -173,9 +173,7 @@ public function testAttributesWithClosure() $matcher = new RequestMatcher(); $request = Request::create('/admin/foo'); - $request->attributes->set('_controller', function () { - return new Response('foo'); - }); + $request->attributes->set('_controller', fn () => new Response('foo')); $matcher->matchAttribute('_controller', 'babar'); $this->assertFalse($matcher->matches($request)); diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 23acd2e05f7fe..fda63830578a2 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -2139,9 +2139,7 @@ public function testSetTrustedHostsDoesNotBreakOnSpecialCharacters() public function testFactory() { - Request::setFactory(function (array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { - return new NewRequest(); - }); + Request::setFactory(fn (array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) => new NewRequest()); $this->assertEquals('foo', Request::create('/')->getFoo()); diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php index aca283af0023d..dc3e5a3ac2752 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php @@ -44,7 +44,7 @@ public static function tearDownAfterClass(): void public function testCookie($fixture) { $result = file_get_contents(sprintf('http://localhost:8054/%s.php', $fixture)); - $result = preg_replace_callback('/expires=[^;]++/', function ($m) { return str_replace('-', ' ', $m[0]); }, $result); + $result = preg_replace_callback('/expires=[^;]++/', fn ($m) => str_replace('-', ' ', $m[0]), $result); $this->assertStringMatchesFormatFile(__DIR__.sprintf('/Fixtures/response-functional/%s.expected', $fixture), $result); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php index aca2bfd882b20..ae470da5d0772 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php @@ -46,7 +46,7 @@ public function testSession($fixture) $context = ['http' => ['header' => "Cookie: sid=123abc\r\n"]]; $context = stream_context_create($context); $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context); - $result = preg_replace_callback('/expires=[^;]++/', function ($m) { return str_replace('-', ' ', $m[0]); }, $result); + $result = preg_replace_callback('/expires=[^;]++/', fn ($m) => str_replace('-', ' ', $m[0]), $result); $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/invalid_regenerate.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/invalid_regenerate.php index 2798442a9d624..d7ec890d99e61 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/invalid_regenerate.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/invalid_regenerate.php @@ -17,4 +17,4 @@ echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty'; echo "\n"; -ob_start(function ($buffer) { return preg_replace('~_sf2_meta.*$~m', '', str_replace(session_id(), 'random_session_id', $buffer)); }); +ob_start(fn ($buffer) => preg_replace('~_sf2_meta.*$~m', '', str_replace(session_id(), 'random_session_id', $buffer))); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php index a0f635c8712ec..b85849595ad0b 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php @@ -7,4 +7,4 @@ session_regenerate_id(true); -ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); }); +ob_start(fn ($buffer) => str_replace(session_id(), 'random_session_id', $buffer)); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php index 96dca3c2c0006..a86c8205623f9 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php @@ -21,4 +21,4 @@ echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty'; -ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); }); +ob_start(fn ($buffer) => str_replace(session_id(), 'random_session_id', $buffer)); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php index fc2c4182895ac..a005362ceba5a 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php @@ -10,4 +10,4 @@ $_SESSION = ['foo' => 'bar']; -ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); }); +ob_start(fn ($buffer) => str_replace(session_id(), 'random_session_id', $buffer)); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php index a28b6fedfc375..13c951ee32e0a 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php @@ -12,4 +12,4 @@ $storage->regenerate(true); -ob_start(function ($buffer) { return preg_replace('~_sf2_meta.*$~m', '', str_replace(session_id(), 'random_session_id', $buffer)); }); +ob_start(fn ($buffer) => preg_replace('~_sf2_meta.*$~m', '', str_replace(session_id(), 'random_session_id', $buffer))); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php index 3e0e7844104b4..8bb307f44ab5b 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php @@ -40,7 +40,7 @@ protected function setUp(): void } $r = new \ReflectionClass(\Memcached::class); - $methodsToMock = array_map(function ($m) { return $m->name; }, $r->getMethods(\ReflectionMethod::IS_PUBLIC)); + $methodsToMock = array_map(fn ($m) => $m->name, $r->getMethods(\ReflectionMethod::IS_PUBLIC)); $methodsToMock = array_diff($methodsToMock, ['getDelayed', 'getDelayedByKey']); $this->memcached = $this->getMockBuilder(\Memcached::class) diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php index 07a82d09a57a5..18151df7da681 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -157,9 +157,7 @@ public function testReadLockedConvertsStreamToString() $selectStmt = $this->createMock(\PDOStatement::class); $insertStmt = $this->createMock(\PDOStatement::class); - $pdo->prepareResult = function ($statement) use ($selectStmt, $insertStmt) { - return str_starts_with($statement, 'INSERT') ? $insertStmt : $selectStmt; - }; + $pdo->prepareResult = fn ($statement) => str_starts_with($statement, 'INSERT') ? $insertStmt : $selectStmt; $content = 'foobar'; $stream = $this->createStream($content); @@ -333,7 +331,7 @@ public function testConfigureSchemaDifferentDatabase() $schema = new Schema(); $pdoSessionHandler = new PdoSessionHandler($this->getMemorySqlitePdo()); - $pdoSessionHandler->configureSchema($schema, fn() => false); + $pdoSessionHandler->configureSchema($schema, fn () => false); $this->assertFalse($schema->hasTable('sessions')); } @@ -342,7 +340,7 @@ public function testConfigureSchemaSameDatabase() $schema = new Schema(); $pdoSessionHandler = new PdoSessionHandler($this->getMemorySqlitePdo()); - $pdoSessionHandler->configureSchema($schema, fn() => true); + $pdoSessionHandler->configureSchema($schema, fn () => true); $this->assertTrue($schema->hasTable('sessions')); } @@ -352,7 +350,7 @@ public function testConfigureSchemaTableExistsPdo() $schema->createTable('sessions'); $pdoSessionHandler = new PdoSessionHandler($this->getMemorySqlitePdo()); - $pdoSessionHandler->configureSchema($schema, fn() => true); + $pdoSessionHandler->configureSchema($schema, fn () => true); $table = $schema->getTable('sessions'); $this->assertEmpty($table->getColumns(), 'The table was not overwritten'); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php index 203c4b2851ff2..6b8b8f111dc6b 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php @@ -72,7 +72,7 @@ public function testCreateRedisHandlerFromDsn() $ttlProperty = $reflection->getProperty('ttl'); $this->assertSame(3600, $ttlProperty->getValue($handler)); - $handler = SessionHandlerFactory::createHandler('redis://localhost?prefix=foo&ttl=3600&ignored=bar', ['ttl' => function () { return 123; }]); + $handler = SessionHandlerFactory::createHandler('redis://localhost?prefix=foo&ttl=3600&ignored=bar', ['ttl' => fn () => 123]); $this->assertInstanceOf(\Closure::class, $reflection->getProperty('ttl')->getValue($handler)); } diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php index 4a08041f33775..b12ce8d35ffd6 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php @@ -197,8 +197,6 @@ private function getClassMethodsWithoutMagicMethods($classOrObject): array { $methods = get_class_methods($classOrObject); - return array_filter($methods, function (string $method) { - return 0 !== strncmp($method, '__', 2); - }); + return array_filter($methods, fn (string $method) => 0 !== strncmp($method, '__', 2)); } } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php index 661b84e0ade9c..b6448ba5edda5 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php @@ -110,9 +110,7 @@ public function getProcessedLogs() } // sort logs from oldest to newest - usort($logs, static function ($logA, $logB) { - return $logA['timestamp'] <=> $logB['timestamp']; - }); + usort($logs, static fn ($logA, $logB) => $logA['timestamp'] <=> $logB['timestamp']); return $this->processedLogs = $logs; } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index aa505783bbd46..cdfa081d8b971 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -136,7 +136,7 @@ public function collect(Request $request, Response $response, \Throwable $except continue; } if ('request_headers' === $key || 'response_headers' === $key) { - $this->data[$key] = array_map(functi 10000 on ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value); + $this->data[$key] = array_map(fn ($v) => isset($v[0]) && !isset($v[1]) ? $v[0] : $v, $value); } } diff --git a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php index 0870b6a241d1e..9e8c27b26e682 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php @@ -127,9 +127,7 @@ public function write(Profile $profile): bool // when there are errors in sub-requests, the parent and/or children tokens // may equal the profile token, resulting in infinite loops $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; - $childrenToken = array_filter(array_map(function (Profile $p) use ($profileToken) { - return $profileToken !== $p->getToken() ? $p->getToken() : null; - }, $profile->getChildren())); + $childrenToken = array_filter(array_map(fn (Profile $p) => $profileToken !== $p->getToken() ? $p->getToken() : null, $profile->getChildren())); // Store profile $data = [ diff --git a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php index 44d962d503f3f..2670ce1ab8f00 100644 --- a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php +++ b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php @@ -7,8 +7,8 @@ +{% endblock %} + {% block toolbar %} {% if collector.firewall %} {% set icon %} 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 b0353b87db310..ca51978f13333 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig @@ -1,5 +1,51 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% if 'unknown' == collector.symfonyState %} {% set block_status = '' %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig index 3dcd475dcd7ea..7f389405141ab 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -41,6 +41,27 @@ {{ parent() }} +{% endblock %} + + {% block toolbar %} {% if collector.requestCount %} {% set icon %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index c4d789e4778c1..d826993ee7ce8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -1,5 +1,220 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% if collector.counterrors or collector.countdeprecations or collector.countwarnings %} {% set icon %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig index e1bdb4c51cd35..8e65365a15ce0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig @@ -1,5 +1,143 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% set events = collector.events %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig index cd1b9ece321ed..d803d6f2f80d0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig @@ -1,5 +1,43 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% if collector.messages|length > 0 %} {% set status_color = collector.exceptionsCount ? 'red' %} @@ -39,25 +77,6 @@ {% endblock %} -{% block head %} - {{ parent() }} - -{% endblock %} - {% block panel %}

    Messages

    diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig index 595ea63175e68..4b9d79494a4c9 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig @@ -1,5 +1,28 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% set request_handler %} {{ _self.set_handler(collector.controller) }} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig index 238444a0052ef..46100b46ca0c0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig @@ -1,5 +1,33 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% if collector.handledCount > 0 %} {% set icon %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig index 57f85cdfe9ec3..c74ee256abb2e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig @@ -1,5 +1,43 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% set has_time_events = collector.events|length > 0 %} {% set total_time = has_time_events ? '%.0f'|format(collector.duration) : 'n/a' %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig index f4626403a88d8..a54cc8ff414c4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig @@ -1,5 +1,42 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% set time = collector.templatecount ? '%0.0f'|format(collector.time) : 'n/a' %} {% set icon %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig index a473ac2372bac..daad8404e4cb3 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig @@ -1,5 +1,34 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% if collector.violationsCount > 0 or collector.calls|length %} {% set status_color = collector.violationsCount ? 'red' %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig index 9f1b5fba66fc4..819b1cc7de7b0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig @@ -11,6 +11,7 @@ button,hr,input{overflow:visible}progress,sub,sup{vertical-align:baseline}[type= --font-size-body: 14px; --font-size-monospace: 13px; --font-variant-ligatures-monospace: none; + --summary-status-border-width: 6px; --white: #fff; @@ -147,8 +148,6 @@ button,hr,input{overflow:visible}progress,sub,sup{vertical-align:baseline}[type= --tab-active-color: var(--color-text); --tab-disabled-background: #f5f5f5; --tab-disabled-color: #999; - --log-filter-active-num-color: #2563EB; - --log-timestamp-color: #555; --code-block-background: var(--gray-50); --metric-value-background: var(--page-background); --metric-border-color: var(--gray-300); @@ -169,9 +168,6 @@ button,hr,input{overflow:visible}progress,sub,sup{vertical-align:baseline}[type= --shadow: 0px 0px 1px rgba(128, 128, 128, .2); --border: 1px solid #e0e0e0; --background-error: var(--color-error); - --mailer-email-table-wrapper-background: var(--gray-100); - --mailer-email-table-active-row-background: #dbeafe; - --mailer-email-table-active-row-color: var(--color-text); --highlight-variable: #e36209; --highlight-string: #22863a; @@ -288,8 +284,6 @@ button,hr,input{overflow:visible}progress,sub,sup{vertical-align:baseline}[type= --tab-active-color: var(--gray-800); --tab-disabled-background: var(--page-background); --tab-disabled-color: var(--gray-400); - --log-filter-active-num-color: #2563EB; - --log-timestamp-color: #ccc; --code-block-background: var(--gray-900); --metric-value-background: var(--page-background); --metric-border-color: var(--gray-500); @@ -310,9 +304,6 @@ button,hr,input{overflow:visible}progress,sub,sup{vertical-align:baseline}[type= --shadow: 0px 0px 1px rgba(32, 32, 32, .2); --border: 1px solid #666; --background-error: #b0413e; - --mailer-email-table-wrapper-background: var(--gray-900); - --mailer-email-table-active-row-background: var(--gray-300); - --mailer-email-table-active-row-color: var(--gray-800); --highlight-variable: #ffa657; --highlight-string: #7ee787; --highlight-comment: #8b949e; @@ -466,11 +457,6 @@ input[type="radio"], input[type="checkbox"] { box-shadow: none; } -time[data-render-as-date], -time[data-render-as-time] { - white-space: nowrap; -} - /* Used to hide elements added for accessibility reasons (the !important modifier is needed here) */ .visually-hidden { border: 0 !important; @@ -673,7 +659,8 @@ table tbody td.num-col { .empty p { font-size: var(--font-size-body); max-width: 60ch; - margin: 1em auto; + margin-left: auto; + margin-right: auto; text-align: center; } .empty.empty-panel { @@ -884,23 +871,6 @@ tr.status-warning td { color: var(--color-error); } -{# Syntax highlighting - ========================================================================= #} -.highlight pre { - margin: 0; - white-space: pre-wrap; -} - -.highlight .keyword { color: var(--highlight-keyword); font-weight: bold; } -.highlight .word { color: var(--color-text); } -.highlight .variable { color: var(--highlight-variable); } -.highlight .symbol { color: var(--color-text); } -.highlight .comment { color: var(--highlight-comment); } -.highlight .backtick { color: var(--color-muted); } -.highlight .string { color: var(--highlight-string); } -.highlight .number { color: var(--highlight-constant); font-weight: bold; } -.highlight .error { color: var(--highlight-error); } - {# Icons ========================================================================= #} .sf-icon { @@ -1471,72 +1441,6 @@ tr.status-warning td { box-shadow: var(--selected-badge-danger-shadow); } -{# Timeline panel - ========================================================================= #} -#timeline-control { - background: var(--table-background); - box-shadow: var(--shadow); - margin: 1em 0; - padding: 10px; -} -#timeline-control label { - font-weight: bold; - margin-right: 1em; -} -#timeline-control input { - background: var(--metric-value-background); - font-size: 16px; - padding: 4px; - text-align: right; - width: 5em; -} -#timeline-control .help { - margin-left: 1em; -} - -.sf-profiler-timeline .legends { - font-size: 12px; - line-height: 1.5em; -} -.sf-profiler-timeline .legends button { - color: var(--color-text); -} -.sf-profiler-timeline + p.help { - margin-top: 0; -} - -{# HttpClient panel - ========================================================================= #} -.sf-profiler-httpclient-requests thead th { - vertical-align: top; -} -.sf-profiler-httpclient-requests .http-method { - border: 1px solid var(--header-status-request-method-color); - border-radius: 5px; - color: var(--header-status-request-method-color); - display: inline-block; - font-weight: 500; - line-height: 1; - margin-right: 6px; - padding: 2px 4px; - text-align: center; - white-space: nowrap; -} -.sf-profiler-httpclient-requests .status-response-status-code { - background: var(--gray-600); - border-radius: 4px; - color: var(--white); - display: inline-block; - font-size: 12px; - font-weight: bold; - margin-bottom: 2px; - padding: 1px 3px; -} -.sf-profiler-httpclient-requests .status-response-status-code.status-success { background: var(--header-success-status-code-background); color: var(--header-success-status-code-color); } -.sf-profiler-httpclient-requests .status-response-status-code.status-warning { background: var(--header-warning-status-code-background); color: var(--header-warning-status-code-color); } -.sf-profiler-httpclient-requests .status-response-status-code.status-error { background: var(--header-error-status-code-background); color: var(--header-error-status-code-color); } - - {# Tabbed navigation ========================================================================= #} .tab-navigation { @@ -1558,10 +1462,8 @@ tr.status-warning td { background: transparent; border: 0; box-shadow: none; - color: var(--color-text); transition: box-shadow .05s ease-in, background-color .05s ease-in; cursor: pointer; - font-size: 14px; font-weight: 500; line-height: 1.4; margin: 0; @@ -1682,59 +1584,7 @@ tr.status-warning td { .filter-list-choice li:before { content: '\2714\00a0'; color: transparent; } .filter-list-choice li.active:before { color: unset; } -{# Twig panel - ========================================================================= #} -#twig-dump pre { - font-size: var(--font-size-monospace); - line-height: 1.7; - background-color: var(--base-0); - border: var(--border); - border-radius: 6px; - padding: 15px; - box-shadow: 0 0 1px rgba(128, 128, 128, .2); -} -#twig-dump span { - border-radius: 2px; - padding: 1px 2px; -} -#twig-dump .status-error { background: transparent; color: var(--color-error); } -#twig-dump .status-warning { background: rgba(240, 181, 24, 0.3); } -#twig-dump .status-success { background: rgba(100, 189, 99, 0.2); } -#twig-dump .status-info { background: var(--info-background); } -.theme-dark #twig-dump .status-warning { color: var(--yellow-200); } -.theme-dark #twig-dump .status-success { color: var(--green-200); } - -#twig-table tbody td { - position: relative; -} -#twig-table tbody td div { - margin: 0; -} -#twig-table .template-file-path { - color: var(--color-muted); - display: block; -} - -{# Request panel - ========================================================================= #} -.empty-query-post-files { - display: flex; - justify-content: space-between; -} -.empty-query-post-files > div { - flex: 1; -} -.empty-query-post-files > div + div { - margin-left: 30px; -} -.empty-query-post-files h3 { - margin-top: 0; -} -.empty-query-post-files .empty { - margin-bottom: 0; -} - -{# Logger panel +{# Badges ========================================================================= #} .badge { background: var(--badge-background); @@ -1753,495 +1603,7 @@ tr.status-warning td { color: var(--badge-warning-color); } -.log-filters { - display: flex; - flex-wrap: wrap; -} -.log-filters .log-filter { - flex-shrink: 0; - margin-right: 15px; - position: relative; -} -.log-filters .log-filter summary { - align-items: center; - background: var(--button-background); - border-radius: 6px; - border: 1px solid var(--button-border-color); - box-shadow: var(--button-box-shadow); - color: var(--button-color); - cursor: pointer; - display: flex; - font-size: 13px; - font-weight: 500; - padding: 4px 8px; - white-space: nowrap; -} -.log-filters .log-filter summary:active { - box-shadow: none; - transform: translateY(1px); -} -.log-filters .log-filter summary .icon { - height: 18px; - width: 18px; - margin: 0 7px 0 0; -} -.log-filters .log-filter summary svg { - height: 18px; - width: 18px; - opacity: 0.7; -} -.log-filters .log-filter summary svg { - stroke-width: 2; -} -.log-filters .log-filter summary .filter-active-num { - color: var(--log-filter-active-num-color); - font-weight: bold; - padding: 0 1px; -} -.log-filter .tab-navigation { - position: relative; -} -.log-filter .tab-navigation input[type="radio"] { - position: absolute; - pointer-events: none; - opacity: 0; -} -.tab-navigation input[type="radio"]:checked + .tab-control { - background-color: var(--tab-active-background); - border-radius: 6px; - box-shadow: inset 0 0 0 1.5px var(--tab-active-border-color); - color: var(--tab-active-color); - position: relative; - z-index: 1; -} -.theme-dark .tab-navigation input[type="radio"]:checked + .tab-control { - box-shadow: inset 0 0 0 1px var(--tab-border-color); -} -.tab-navigation input[type="radio"]:focus-visible + .tab-control { - outline: 1px solid var(--color-link); -} -.tab-navigation input[type="radio"]:checked + .tab-control + input[type="radio"] + .tab-control:before{ - width: 0; -} - -.log-filters .log-filter .log-filter-content { - background: var(--base-0); - border: 1px solid var(--table-border-color); - border-radius: 6px; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); - padding: 15px; - position: absolute; - left: 0; - top: 32px; - max-width: 400px; - min-width: 200px; - z-index: 9999; -} -.log-filters .log-filter .log-filter-content .log-filter-option { - align-items: center; - display: flex; -} -.log-filter .filter-select-all-or-none { - margin-bottom: 10px; -} -.log-filter .filter-select-all-or-none button + button { - margin-left: 15px; -} -.log-filters .log-filter .log-filter-content .log-filter-option + .log-filter-option { - margin: 7px 0 0; -} -.log-filters .log-filter .log-filter-content .log-filter-option label { - cursor: pointer; - flex: 1; - padding-left: 10px; -} - -table.logs { - border-bottom-width: 0; - border-collapse: collapse; -} -table.logs tr + tr td { - border-width: 1px 0 0; -} -table.logs .metadata { - display: block; - font-size: 12px; -} -.theme-dark tr.status-error td, -.theme-dark tr.status-warning td { border-bottom: unset; border-top: unset; } - -table.logs .log-timestamp { - color: var(--log-timestamp-color); -} -table.logs .log-metadata { - margin: 8px 0 0; -} -table.logs .log-metadata > span { - display: inline-block; -} -table.logs .log-metadata > span + span { - margin-left: 10px; -} -table.logs .log-metadata .log-channel { - color: var(--base-1); - font-size: 13px; - font-weight: bold; -} -table.logs .log-metadata .badge { - background: var(--badge-light-background); - color: var(--badge-light-color); -} -table.logs .log-metadata .log-num-occurrences { - color: var(--color-muted); - font-size: 13px; -} -table.logs .log-metadata .context { - background: var(--code-block-background); - border-radius: 4px; - padding: 5px; -} -table.logs .log-metadata .context + .context { - margin-top: 10px; -} -.log-type-badge { - background: var(--badge-light-background); - box-shadow: none; - color: var(--badge-light-color); - display: inline-block; - font-family: var(--font-family-system); - margin-top: 5px; -} -.log-type-badge.badge-deprecation, -.log-type-badge.badge-warning { - background: var(--badge-warning-background); - color: var(--badge-warning-color); -} -.log-type-badge.badge-error { - background: var(--badge-danger-background); - color: var(--badge-danger-color); -} -.log-type-badge.badge-silenced { - background: #EDE9FE; - color: #6D28D9; -} -.theme-dark .log-type-badge.badge-silenced { - background: #5B21B6; - color: #EDE9FE; -} - -tr.log-status-warning > td:first-child, -tr.log-status-error > td:first-child, -tr.log-status-silenced > td:first-child { - position: relative; -} -tr.log-status-warning > td:first-child:before, -tr.log-status-error > td:first-child:before, -tr.log-status-silenced > td:first-child:before { - background: transparent; - border-radius: 0; - content: ''; - position: absolute; - top: 0; - left: 0; - width: 4px; - height: 100%; -} -tr.log-status-warning > td:first-child:before { - background: var(--yellow-400); -} -tr.log-status-error > td:first-child:before { - background: var(--red-400); -} -tr.log-status-silenced > td:first-child:before { - background: #a78bfa; -} - -.container-compilation-logs { - background: var(--table-background); - border: 1px solid var(--base-2); - border-radius: 6px; - margin-top: 30px; - padding: 15px; -} -.container-compilation-logs summary { - cursor: pointer; -} -.container-compilation-logs summary h4 { - margin: 0 0 5px; -} -.container-compilation-logs summary p { - margin: 0; -} - -{# Mailer panel - ========================================================================= #} -.mailer-email-summary-table-wrapper { - background: var(--mailer-email-table-wrapper-background); - border-bottom: 4px double var(--table-border-color); - border-radius: inherit; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - margin: 0 -9px 10px -9px; - padding-bottom: 10px; - transform: translateY(-9px); - max-height: 265px; - overflow-y: auto; -} -.mailer-email-summary-table, -.mailer-email-summary-table tr, -.mailer-email-summary-table td { - border: 0; - border-radius: inherit; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - box-shadow: none; -} -.mailer-email-summary-table th { - color: var(--color-muted); - font-size: 13px; - padding: 4px 10px; -} -.mailer-email-summary-table tr td, -.mailer-email-summary-table tr:last-of-type td { - border: solid var(--table-border-color); - border-width: 1px 0; -} -.mailer-email-summary-table-row { - margin: 5px 0; -} -.mailer-email-summary-table-row:hover { - cursor: pointer; -} -.mailer-email-summary-table-row.active { - background: var(--mailer-email-table-active-row-background); - color: var(--mailer-email-table-active-row-color); -} -.mailer-email-summary-table-row td { - font-family: var(--font-family-system); - font-size: inherit; -} -.mailer-email-details { - display: none; -} -.mailer-email-details.active { - display: block; -} -.mailer-transport-information { - border-bottom: 1px solid var(--form-input-border-color); - padding-bottom: 5px; - font-size: var(--font-size-body); - margin: 5px 0 10px 5px; -} -.mailer-transport-information .badge { - font-size: inherit; - font-weight: inherit; -} -.mailer-message-subject { - font-size: 21px; - font-weight: bold; - margin: 5px; -} -.mailer-message-headers { - margin-bottom: 10px; -} -.mailer-message-headers p { - font-size: var(--font-size-body); - margin: 2px 5px; -} -.mailer-message-header-secondary { - color: var(--color-muted); -} -.mailer-message-attachments-title { - align-items: center; - display: flex; - font-size: var(--font-size-body); - font-weight: 600; - margin-bottom: 10px; -} -.mailer-message-attachments-title svg { - color: var(--color-muted); - margin-right: 5px; - height: 18px; - width: 18px; -} -.mailer-message-attachments-title span { - font-weight: normal; - margin-left: 4px; -} -.mailer-message-attachments-list { - list-style: none; - margin: 0 0 5px 20px; - padding: 0; -} -.mailer-message-attachments-list li { - align-items: center; - display: flex; -} -.mailer-message-attachments-list li svg { - margin-right: 5px; - height: 18px; - width: 18px; -} -.mailer-message-attachments-list li a { - margin-left: 5px; -} -.mailer-email-body { - margin: 0; - padding: 6px 8px; -} -.mailer-empty-email-body { - background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23e5e5e5' stroke-width='4' stroke-dasharray='6%2c 14' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e"); - border-radius: 6px; - color: var(--color-muted); - margin: 1em 0 0; - padding: .5em 1em; -} -.theme-dark .mailer-empty-email-body { - background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23737373' stroke-width='4' stroke-dasharray='6%2c 14' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e"); -} -.mailer-empty-email-body p { - font-size: var(--font-size-body); - margin: 0; - padding: 0.5em 0; -} - -.mailer-message-download-raw { - align-items: center; - display: flex; - padding: 5px 0 0 5px; -} -.mailer-message-download-raw svg { - height: 18px; - width: 18px; - margin-right: 3px; -} - -{# Doctrine panel - ========================================================================= #} -.sql-runnable { - background: var(--base-1); - margin: .5em 0; - padding: 1em; -} -.sql-explain { - overflow-x: auto; - max-width: 888px; -} -.width-full .sql-explain { - max-width: min-content; -} -.sql-explain table td, .sql-explain table tr { - word-break: normal; -} -.queries-table pre { - margin: 0; - white-space: pre-wrap; - -ms-word-break: break-all; - word-break: break-all; - word-break: break-word; - -webkit-hyphens: auto; - -moz-hyphens: auto; - hyphens: auto; -} - -{# Security panel - ========================================================================= #} -#collector-content .decision-log .voter_result td { - border-top-width: 1px; - border-bottom-width: 0; - padding-bottom: 0; -} - -#collector-content .decision-log .voter_details td { - border-top-width: 0; - border-bottom-width: 1px; - padding-bottom: 0; -} - -#collector-content .decision-log .voter_details table { - border: 0; - margin: 0; - box-shadow: unset; -} - -#collector-content .decision-log .voter_details table td { - border: 0; - padding: 0 0 8px 0; -} - -{# Validator panel - ========================================================================= #} - -#collector-content .sf-validator { - margin-bottom: 2em; -} - -#collector-content .sf-validator .sf-validator-context, -#collector-content .sf-validator .trace { - border: var(--border); - background: var(--base-0); - padding: 10px; - margin: 0.5em 0; - overflow: auto; -} -#collector-content .sf-validator .trace { - font-size: 12px; -} -#collector-content .sf-validator .trace li { - margin-bottom: 0; - padding: 0; -} -#collector-content .sf-validator .trace li.selected { - background: var(--highlight-selected-line); -} - -{# Serializer panel - ========================================================================= #} - -#collector-content .sf-serializer { - margin-bottom: 2em; -} - -#collector-content .sf-serializer .trace { - border: var(--border); - background: var(--base-0); - padding: 10px; - margin: 0.5em 0; - overflow: auto; -} -#collector-content .sf-serializer .trace { - font-size: 12px; -} -#collector-content .sf-serializer .trace li { - margin-bottom: 0; - padding: 0; -} -#collector-content .sf-serializer .trace li.selected { - background: var(--highlight-selected-line); -} - -{# Messenger panel - ========================================================================= #} - -#collector-content .message-bus .trace { - border: var(--border); - background: var(--base-0); - padding: 10px; - margin: 0.5em 0; - overflow: auto; -} -#collector-content .message-bus .trace { - font-size: 12px; -} -#collector-content .message-bus .trace li { - margin-bottom: 0; - padding: 0; -} -#collector-content .message-bus .trace li.selected { - background: var(--highlight-selected-line); -} - -{# Dump panel +{# Dumped contents (used in many different panels ========================================================================= #} pre.sf-dump, pre.sf-dump .sf-dump-default { white-space: pre-wrap; @@ -2299,93 +1661,85 @@ pre.sf-dump, pre.sf-dump .sf-dump-default { padding: 0; } -{# Forms panel - ========================================================================= #} -.form-type-class { - font-size: var(--font-size-body); - display: flex; - margin: 0 0 15px; -} -.form-type-class-label { - margin-right: 4px; -} -.form-type-class pre.sf-dump { - font-family: var(--font-family-system) !important; - font-size: var(--font-size-body) !important; - margin-left: 5px; -} -.form-type-class .sf-dump .sf-dump-str { - color: var(--color-link) !important; - text-decoration: underline; -} -.form-type-class .sf-dump .sf-dump-str:hover { - text-decoration: none; +#collector-content pre.sf-dump, #collector-content .sf-dump code, #collector-content .sf-dump samp { + font-size: var(--font-size-monospace); + font-weight: normal; } -{# Configuration panel - ========================================================================= #} -.config-symfony-version-lts { - border: 0; - color: var(--color-muted); - font-size: 21px; - line-height: 33px; -} -.config-symfony-version-lts[title] { - text-decoration: none; -} -.config-symfony-version-status-badge { - background-color: var(--badge-background); - border-radius: 4px; - color: var(--badge-color); - display: inline-block; - font-size: 15px; - font-weight: bold; - margin: 10px 0 5px; - padding: 3px 7px; - white-space: nowrap; -} -.config-symfony-version-status-badge.status-success { - background-color: var(--badge-success-background); - color: var(--badge-success-color); +#collector-content .sf-dump pre.sf-dump, +#collector-content .sf-dump .trace { + background: var(--page-background); } -.config-symfony-version-status-badge.status-warning { - background-color: var(--badge-warning-background); - color: var(--badge-warning-color); +#collector-content pre.sf-dump, +#collector-content .sf-dump-default { + color: var(--color-text); } -.config-symfony-version-status-badge.status-error { - background-color: var(--badge-danger-background); - color: var(--badge-danger-color); +#collector-content .sf-dump samp { + line-height: 1.7; } -.config-symfony-version-roadmap-link { - display: inline-block; - margin: 10px 5px 5px; +body.theme-light #collector-content .sf-dump-expanded { color: var(--color-text); } +body.theme-light #collector-content .sf-dump-str { color: var(--highlight-string); } +body.theme-light #collector-content .sf-dump-private, +body.theme-light #collector-content .sf-dump-protected, +body.theme-light #collector-content .sf-dump-public { color: var(--color-text); } +body.theme-light #collector-content .sf-dump-note { color: #e36209; } +body.theme-light #collector-content .sf-dump-meta { color: #6f42c1; } +body.theme-light #collector-content .sf-dump-key { color: #067d17; } +body.theme-light #collector-content .sf-dump-num, +body.theme-light #collector-content .sf-dump-const { color: var(--highlight-constant); } +body.theme-light #collector-content .sf-dump-ref { color: #6E6E6E; } +body.theme-light #collector-content .sf-dump-ellipsis { color: var(--gray-600); max-width: 100em; } +body.theme-light #collector-content .sf-dump-ellipsis-path { max-width: 5em; } +body.theme-light #collector-content .sf-dump .trace li.selected { + background: rgba(255, 255, 153, 0.5); } -.config-symfony-eol { - margin-top: 5px; +body.theme-dark #collector-content .sf-dump-expanded { color: var(--color-text); } +body.theme-dark #collector-content .sf-dump-str { color: var(--highlight-string); } +body.theme-dark #collector-content .sf-dump-private, +body.theme-dark #collector-content .sf-dump-protected, +body.theme-dark #collector-content .sf-dump-public { color: var(--color-text); } +body.theme-dark #collector-content .sf-dump-note { color: #ffa657; } +body.theme-dark #collector-content .sf-dump-meta { color: #d2a8ff; } +body.theme-dark #collector-content .sf-dump-key { color: #a5d6ff; } +body.theme-dark #collector-content .sf-dump-num, +body.theme-dark #collector-content .sf-dump-const { color: var(--highlight-constant); } +body.theme-dark #collector-content .sf-dump-ref { color: var(--gray-400); } +body.theme-dark #collector-content .sf-dump-ellipsis { color: var(--gray-300); max-width: 100em; } +body.theme-dark #collector-content .sf-dump-ellipsis-path { max-width: 5em; } +body.theme-dark #collector-content .sf-dump .trace li.selected { + background: rgba(255, 255, 153, 0.5); } -{# Search Results page +{# Doctrine panel ========================================================================= #} -#search-results td { - font-family: var(--font-family-system); - vertical-align: middle; +.sql-runnable { + background: var(--base-1); + margin: .5em 0; + padding: 1em; } - -#search-results .sf-search { - - visibility: hidden; - margin-left: 2px; +.sql-explain { + overflow-x: auto; + max-width: 888px; } -#search-results tr:hover .sf-search { - visibility: visible; +.width-full .sql-explain { + max-width: min-content; } -#search-results .sf-search svg { - stroke-width: 3; +.sql-explain table td, .sql-explain table tr { + word-break: normal; +} +.queries-table pre { + margin: 0; + white-space: pre-wrap; + -ms-word-break: break-all; + word-break: break-all; + word-break: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; } {# Small screens ========================================================================= #} - .visible-small { display: none; } @@ -2458,54 +1812,3 @@ body.width-full .container { @media (min-width: 1200px) { body.width-full .container { margin: 0 30px; } } - -{# VarDumper dumps - ========================================================================= #} -#collector-content pre.sf-dump, #collector-content .sf-dump code, #collector-content .sf-dump samp { - font-size: var(--font-size-monospace); - font-weight: normal; -} - -#collector-content .sf-dump pre.sf-dump, -#collector-content .sf-dump .trace { - background: var(--page-background); -} -#collector-content pre.sf-dump, -#collector-content .sf-dump-default { - color: var(--color-text); -} -#collector-content .sf-dump samp { - line-height: 1.7; -} -body.theme-light #collector-content .sf-dump-expanded { color: var(--color-text); } -body.theme-light #collector-content .sf-dump-str { color: var(--highlight-string); } -body.theme-light #collector-content .sf-dump-private, -body.theme-light #collector-content .sf-dump-protected, -body.theme-light #collector-content .sf-dump-public { color: var(--color-text); } -body.theme-light #collector-content .sf-dump-note { color: #e36209; } -body.theme-light #collector-content .sf-dump-meta { color: #6f42c1; } -body.theme-light #collector-content .sf-dump-key { color: #067d17; } -body.theme-light #collector-content .sf-dump-num, -body.theme-light #collector-content .sf-dump-const { color: var(--highlight-constant); } -body.theme-light #collector-content .sf-dump-ref { color: #6E6E6E; } -body.theme-light #collector-content .sf-dump-ellipsis { color: var(--gray-600); max-width: 100em; } -body.theme-light #collector-content .sf-dump-ellipsis-path { max-width: 5em; } -body.theme-light #collector-content .sf-dump .trace li.selected { - background: rgba(255, 255, 153, 0.5); -} -body.theme-dark #collector-content .sf-dump-expanded { color: var(--color-text); } -body.theme-dark #collector-content .sf-dump-str { color: var(--highlight-string); } -body.theme-dark #collector-content .sf-dump-private, -body.theme-dark #collector-content .sf-dump-protected, -body.theme-dark #collector-content .sf-dump-public { color: var(--color-text); } -body.theme-dark #collector-content .sf-dump-note { color: #ffa657; } -body.theme-dark #collector-content .sf-dump-meta { color: #d2a8ff; } -body.theme-dark #collector-content .sf-dump-key { color: #a5d6ff; } -body.theme-dark #collector-content .sf-dump-num, -body.theme-dark #collector-content .sf-dump-const { color: var(--highlight-constant); } -body.theme-dark #collector-content .sf-dump-ref { color: var(--gray-400); } -body.theme-dark #collector-content .sf-dump-ellipsis { color: var(--gray-300); max-width: 100em; } -body.theme-dark #collector-content .sf-dump-ellipsis-path { max-width: 5em; } -body.theme-dark #collector-content .sf-dump .trace li.selected { - background: rgba(255, 255, 153, 0.5); -} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig index 26fcff3d242d2..befa301a1508f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/results.html.twig @@ -6,6 +6,28 @@ {%- endif -%} {% endmacro %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block summary %}

    Profile Search

    From 4173c3884728cb1807acc0946652109091e54881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Tue, 13 Dec 2022 22:15:15 +0100 Subject: [PATCH 151/475] [Cache] Add Relay support --- .github/patch-types.php | 1 + .github/workflows/integration-tests.yml | 10 +- .github/workflows/psalm.yml | 8 + .php-cs-fixer.dist.php | 1 + .../Component/Cache/Adapter/RedisAdapter.php | 2 +- .../Cache/Adapter/RedisTagAwareAdapter.php | 18 +- src/Symfony/Component/Cache/CHANGELOG.md | 5 + .../Adapter/RelayAdapterSentinelTest.php | 42 + .../Cache/Tests/Adapter/RelayAdapterTest.php | 56 + .../Cache/Tests/Traits/RedisProxiesTest.php | 36 +- .../Component/Cache/Traits/RedisTrait.php | 80 +- .../Component/Cache/Traits/RelayProxy.php | 1262 +++++++++++++++++ 12 files changed, 1478 insertions(+), 43 deletions(-) create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterSentinelTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterTest.php create mode 100644 src/Symfony/Component/Cache/Traits/RelayProxy.php diff --git a/.github/patch-types.php b/.github/patch-types.php index eaa085bfae9bb..811f74311eb86 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -54,6 +54,7 @@ case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionUnionTypeWithIntersectionFixture.php'): case false !== strpos($file, '/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyProxy/ReadOnlyClass.php'): + case false !== strpos($file, '/src/Symfony/Component/Cache/Traits/RelayProxy.php'): continue 2; } diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 171eab250982e..618763da4544e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -134,11 +134,19 @@ jobs: uses: shivammathur/setup-php@v2 with: coverage: "none" - extensions: "json,couchbase-3.2.2,memcached,mongodb-1.12.0,redis,rdkafka,xsl,ldap" + extensions: "json,couchbase-3.2.2,memcached,mongodb-1.12.0,redis,rdkafka,xsl,ldap,msgpack,igbinary" ini-values: date.timezone=UTC,memory_limit=-1,default_socket_timeout=10,session.gc_probability=0,apc.enable_cli=1,zend.assertions=1 php-version: "${{ matrix.php }}" tools: pecl + - name: Install Relay + run: | + curl -L "https://builds.r2.relay.so/dev/relay-dev-php${{ matrix.php }}-debian-x86-64.tar.gz" | tar xz + cd relay-dev-php${{ matrix.php }}-debian-x86-64 + sudo cp relay.ini $(php-config --ini-dir) + sudo cp relay-pkg.so $(php-config --extension-dir)/relay.so + sudo sed -i "s/00000000-0000-0000-0000-000000000000/$(cat /proc/sys/kernel/random/uuid)/" $(php-config --extension-dir)/relay.so + - name: Display versions run: | php -r 'foreach (get_loaded_extensions() as $extension) echo $extension . " " . phpversion($extension) . PHP_EOL;' diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 26d816ccb6c63..e4d8ccfe4c1b8 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -28,6 +28,14 @@ jobs: ini-values: "memory_limit=-1" coverage: none + - name: Install Relay + run: | + curl -L "https://builds.r2.relay.so/dev/relay-dev-php8.1-debian-x86-64.tar.gz" | tar xz + cd relay-dev-php8.1-debian-x86-64 + sudo cp relay.ini $(php-config --ini-dir) + sudo cp relay-pkg.so $(php-config --extension-dir)/relay.so + sudo sed -i "s/00000000-0000-0000-0000-000000000000/$(cat /proc/sys/kernel/random/uuid)/" $(php-config --extension-dir)/relay.so + - name: Checkout target branch uses: actions/checkout@v3 with: diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index db6a854c1f398..7245326249db2 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -67,6 +67,7 @@ // stop removing spaces on the end of the line in strings ->notPath('Symfony/Component/Messenger/Tests/Command/FailedMessagesShowCommandTest.php') // auto-generated proxies + ->notPath('Symfony/Component/Cache/Traits/RelayProxy.php') ->notPath('Symfony/Component/Cache/Traits/Redis5Proxy.php') ->notPath('Symfony/Component/Cache/Traits/Redis6Proxy.php') ->notPath('Symfony/Component/Cache/Traits/RedisCluster5Proxy.php') diff --git a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php index 9cb8d58991d79..d8e37b1d7b2f3 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php @@ -18,7 +18,7 @@ class RedisAdapter extends AbstractAdapter { use RedisTrait; - public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Relay\Relay $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) { $this->init($redis, $namespace, $defaultLifetime, $marshaller); } diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php index a004709ba504d..b14c26db80455 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php @@ -16,6 +16,7 @@ use Predis\Connection\Aggregate\ReplicationInterface; use Predis\Response\ErrorInterface; use Predis\Response\Status; +use Relay\Relay; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Exception\LogicException; @@ -59,18 +60,19 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter private string $redisEvictionPolicy; private string $namespace; - public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + public function __construct(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) { if ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof ClusterInterface && !$redis->getConnection() instanceof PredisCluster) { throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, get_debug_type($redis->getConnection()))); } - if (\defined('Redis::OPT_COMPRESSION') && \in_array($redis::class, [\Redis::class, \RedisArray::class, \RedisCluster::class], true)) { - $compression = $redis->getOption(\Redis::OPT_COMPRESSION); + $isRelay = $redis instanceof Relay; + if ($isRelay || \defined('Redis::OPT_COMPRESSION') && \in_array($redis::class, [\Redis::class, \RedisArray::class, \RedisCluster::class], true)) { + $compression = $redis->getOption($isRelay ? Relay::OPT_COMPRESSION : \Redis::OPT_COMPRESSION); 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.', static::class, DeflateMarshaller::class)); + if ($isRelay ? Relay::COMPRESSION_NONE : \Redis::COMPRESSION_NONE !== $c) { + throw new InvalidArgumentException(sprintf('redis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); } } } @@ -154,7 +156,7 @@ protected function doDeleteYieldTags(array $ids): iterable }); foreach ($results as $id => $result) { - if ($result instanceof \RedisException || $result instanceof ErrorInterface) { + if ($result instanceof \RedisException || $result instanceof \Relay\Exception || $result instanceof ErrorInterface) { CacheItem::log($this->logger, 'Failed to delete key "{key}": '.$result->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $result]); continue; @@ -221,7 +223,7 @@ protected function doInvalidate(array $tagIds): bool $results = $this->pipeline(function () use ($tagIds, $lua) { if ($this->redis instanceof \Predis\ClientInterface) { $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; - } elseif (\is_array($prefix = $this->redis->getOption(\Redis::OPT_PREFIX) ?? '')) { + } elseif (\is_array($prefix = $this->redis->getOption($this->redis instanceof Relay ? Relay::OPT_PREFIX : \Redis::OPT_PREFIX) ?? '')) { $prefix = current($prefix); } @@ -242,7 +244,7 @@ protected function doInvalidate(array $tagIds): bool $success = true; foreach ($results as $id => $values) { - if ($values instanceof \RedisException || $values instanceof ErrorInterface) { + if ($values instanceof \RedisException || $values instanceof \Relay\Exception || $values instanceof ErrorInterface) { CacheItem::log($this->logger, 'Failed to invalidate key "{key}": '.$values->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $values]); $success = false; diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 902d0ef29cb24..635a9408176b9 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add support for Relay PHP extension for Redis + 6.1 --- diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterSentinelTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterSentinelTest.php new file mode 100644 index 0000000000000..60f506f931871 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterSentinelTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Adapter; + +use PHPUnit\Framework\SkippedTestSuiteError; +use Relay\Relay; +use Relay\Sentinel; +use Symfony\Component\Cache\Adapter\AbstractAdapter; + +/** + * @group integration + */ +class RelayAdapterSentinelTest extends AbstractRedisAdapterTest +{ + public static function setUpBeforeClass(): void + { + if (!class_exists(Sentinel::class)) { + throw new SkippedTestSuiteError('The Relay\Sentinel class is required.'); + } + if (!$hosts = getenv('REDIS_SENTINEL_HOSTS')) { + throw new SkippedTestSuiteError('REDIS_SENTINEL_HOSTS env var is not defined.'); + } + if (!$service = getenv('REDIS_SENTINEL_SERVICE')) { + throw new SkippedTestSuiteError('REDIS_SENTINEL_SERVICE env var is not defined.'); + } + + self::$redis = AbstractAdapter::createConnection( + 'redis:?host['.str_replace(' ', ']&host[', $hosts).']', + ['redis_sentinel' => $service, 'prefix' => 'prefix_', 'class' => Relay::class], + ); + self::assertInstanceOf(Relay::class, self::$redis); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterTest.php new file mode 100644 index 0000000000000..e98fc9bcf69e1 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Adapter; + +use PHPUnit\Framework\SkippedTestSuiteError; +use Relay\Relay; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Adapter\RedisAdapter; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\RelayProxy; + +/** + * @requires extension relay + * + * @group integration + */ +class RelayAdapterTest extends AbstractRedisAdapterTest +{ + public static function setUpBeforeClass(): void + { + try { + new Relay(...explode(':', getenv('REDIS_HOST'))); + } catch (\Relay\Exception $e) { + throw new SkippedTestSuiteError(getenv('REDIS_HOST').': '.$e->getMessage()); + } + self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST'), ['lazy' => true, 'class' => Relay::class]); + self::assertInstanceOf(RelayProxy::class, self::$redis); + } + + public function testCreateHostConnection() + { + $redis = RedisAdapter::createConnection('redis://'.getenv('REDIS_HOST').'?class=Relay\Relay'); + $this->assertInstanceOf(Relay::class, $redis); + $this->assertTrue($redis->isConnected()); + $this->assertSame(0, $redis->getDbNum()); + } + + public function testLazyConnection() + { + $redis = RedisAdapter::createConnection('redis://nonexistenthost?class=Relay\Relay&lazy=1'); + $this->assertInstanceOf(RelayProxy::class, $redis); + // no exception until now + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Failed to resolve host address'); + $redis->getHost(); // yep, only here exception is thrown + } +} diff --git a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php index 27fabf700af8a..dbfcef951cae7 100644 --- a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php +++ b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php @@ -12,15 +12,15 @@ namespace Symfony\Component\Cache\Tests\Traits; use PHPUnit\Framework\TestCase; +use Relay\Relay; use Symfony\Component\VarExporter\LazyProxyTrait; use Symfony\Component\VarExporter\ProxyHelper; -/** - * @requires extension redis - */ class RedisProxiesTest extends TestCase { /** + * @requires extension redis + * * @testWith ["Redis"] * ["RedisCluster"] */ @@ -50,6 +50,36 @@ public function testRedis5Proxy($class) } /** + * @requires extension relay + */ + public function testRelayProxy() + { + $proxy = file_get_contents(\dirname(__DIR__, 2).'/Traits/RelayProxy.php'); + $proxy = substr($proxy, 0, 8 + strpos($proxy, "\n ];" 10000 )); + $methods = []; + + foreach ((new \ReflectionClass(Relay::class))->getMethods() as $method) { + if ('reset' === $method->name || method_exists(LazyProxyTrait::class, $method->name) || $method->isStatic()) { + continue; + } + $return = $method->getReturnType() instanceof \ReflectionNamedType && 'void' === (string) $method->getReturnType() ? '' : 'return '; + $methods[] = "\n ".ProxyHelper::exportSignature($method, false)."\n".<<lazyObjectReal->{$method->name}(...\\func_get_args()); + } + + EOPHP; + } + + uksort($methods, 'strnatcmp'); + $proxy .= implode('', $methods)."}\n"; + + $this->assertStringEqualsFile(\dirname(__DIR__, 2).'/Traits/RelayProxy.php', $proxy); + } + + /** + * @requires extension redis + * * @testWith ["Redis", "redis"] * ["RedisCluster", "redis_cluster"] */ diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 131b9f3268f1e..a226fad13bf7c 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -17,6 +17,8 @@ use Predis\Connection\Aggregate\ReplicationInterface; use Predis\Response\ErrorInterface; use Predis\Response\Status; +use Relay\Relay; +use Relay\Sentinel; use Symfony\Component\Cache\Exception\CacheException; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Marshaller\DefaultMarshaller; @@ -45,10 +47,10 @@ trait RedisTrait 'failover' => 'none', 'ssl' => null, // see https://php.net/context.ssl ]; - private \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; private MarshallerInterface $marshaller; - private function init(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller) + private function init(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller) { parent::__construct($namespace, $defaultLifetime); @@ -80,7 +82,7 @@ private function init(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $ * * @throws InvalidArgumentException when the DSN is invalid */ - public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|Relay { if (str_starts_with($dsn, 'redis:')) { $scheme = 'redis'; @@ -165,28 +167,39 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra $params += $query + $options + self::$defaultConnectionOptions; - if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class)) { - throw new CacheException('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher.'); + if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + throw new CacheException('Redis Sentinel support requires one of: "predis/predis", "ext-redis >= 5.2", "ext-relay".'); } if ($params['redis_cluster'] && isset($params['redis_sentinel'])) { throw new InvalidArgumentException('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.'); } - if (null === $params['class'] && \extension_loaded('redis')) { - $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) && !isset($params['redis_sentinel']) ? \RedisArray::class : \Redis::class); - } else { - $class = $params['class'] ?? \Predis\Client::class; - - if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class)) { - throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and ext-redis >= 5.2 not found.', $class)); - } + $class = $params['class'] ?? match (true) { + $params['redis_cluster'] => \extension_loaded('redis') ? \RedisCluster::class : \Predis\Client::class, + isset($params['redis_sentinel']) => match (true) { + \extension_loaded('redis') => \Redis::class, + \extension_loaded('relay') => Relay::class, + default => \Predis\Client::class, + }, + 1 < \count($hosts) && \extension_loaded('redis') => 1 < \count($hosts) ? \RedisArray::class : \Redis::class, + \extension_loaded('redis') => \Redis::class, + \extension_loaded('relay') => Relay::class, + default => \Predis\Client::class, + }; + + if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and neither ext-redis >= 5.2 nor ext-relay have been found.', $class)); } - if (is_a($class, \Redis::class, true)) { + $isRedisExt = is_a($class, \Redis::class, true); + $isRelayExt = !$isRedisExt && is_a($class, Relay::class, true); + + if ($isRedisExt || $isRelayExt) { $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect'; - $initializer = static function () use ($class, $connect, $params, $auth, $hosts, $tls) { + $initializer = static function () use ($class, $isRedisExt, $connect, $params, $auth, $hosts, $tls) { + $sentinelClass = $isRedisExt ? \RedisSentinel::class : Sentinel::class; $redis = new $class(); $hostIndex = 0; do { @@ -205,7 +218,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra if (\defined('Redis::OPT_NULL_MULTIBULK_AS_NULL') && isset($params['auth'])) { $extra = [$params['auth']]; } - $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...$extra); + $sentinel = new $sentinelClass($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...$extra); if ($address = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { [$host, $port] = $address; @@ -223,7 +236,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra if (isset($params['auth'])) { $extra['auth'] = $params['auth']; } - @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [$extra] : []); + @$redis->{$connect}($host, $port, (float) $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') || !$isRedisExt ? [$extra] : []); set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); try { @@ -243,17 +256,21 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra throw new InvalidArgumentException('Redis connection failed: '.$e.'.'); } - if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { - $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); } return $redis; }; - $redis = $params['lazy'] ? RedisProxy::createLazyProxy($initializer) : $initializer(); + if ($params['lazy']) { + $redis = $isRedisExt ? RedisProxy::createLazyProxy($initializer) : RelayProxy::createLazyProxy($initializer); + } else { + $redis = $initializer(); + } } elseif (is_a($class, \RedisArray::class, true)) { foreach ($hosts as $i => $host) { $hosts[$i] = match ($host['scheme']) { @@ -271,11 +288,11 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); } - if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { - $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } } elseif (is_a($class, \RedisCluster::class, true)) { - $initializer = static function () use ($class, $params, $hosts) { + $initializer = static function () use ($isRedisExt, $class, $params, $hosts) { foreach ($hosts as $i => $host) { $hosts[$i] = match ($host['scheme']) { 'tcp' => $host['host'].':'.$host['port'], @@ -290,8 +307,8 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); } - if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { - $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { + $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, match ($params['failover']) { 'error' => \RedisCluster::FAILOVER_ERROR, @@ -343,7 +360,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra $redis->getConnection()->setSentinelTimeout($params['timeout']); } } elseif (class_exists($class, false)) { - throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\ClientInterface".', $class)); + throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster", "Relay\Relay" nor "Predis\ClientInterface".', $class)); } else { throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); } @@ -413,7 +430,10 @@ protected function doClear(string $namespace): bool $info = $host->info('Server'); $info = !$info instanceof ErrorInterface ? $info['Server'] ?? $info : ['redis_version' => '2.0']; - if (!$host instanceof \Predis\ClientInterface) { + if ($host instanceof Relay) { + $prefix = Relay::SCAN_PREFIX & $host->getOption(Relay::OPT_SCAN) ? '' : $host->getOption(Relay::OPT_PREFIX); + $prefixLen = \strlen($host->getOption(Relay::OPT_PREFIX) ?? ''); + } elseif (!$host instanceof \Predis\ClientInterface) { $prefix = \defined('Redis::SCAN_PREFIX') && (\Redis::SCAN_PREFIX & $host->getOption(\Redis::OPT_SCAN)) ? '' : $host->getOption(\Redis::OPT_PREFIX); $prefixLen = \strlen($host->getOption(\Redis::OPT_PREFIX) ?? ''); } @@ -549,7 +569,7 @@ private function pipeline(\Closure $generator, object $redis = null): \Generator $results[$k] = $connections[$h][$c]; } } else { - $redis->multi(\Redis::PIPELINE); + $redis->multi($redis instanceof Relay ? Relay::PIPELINE : \Redis::PIPELINE); foreach ($generator() as $command => $args) { $redis->{$command}(...$args); $ids[] = 'eval' === $command ? $args[1][0] : $args[0]; @@ -558,7 +578,7 @@ private function pipeline(\Closure $generator, object $redis = null): \Generator } if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) { - $e = new \RedisException($redis->getLastError()); + $e = $redis instanceof Relay ? new \Relay\Exception($redis->getLastError()) : new \RedisException($redis->getLastError()); $results = array_map(fn ($v) => false === $v ? $e : $v, (array) $results); } diff --git a/src/Symfony/Component/Cache/Traits/RelayProxy.php b/src/Symfony/Component/Cache/Traits/RelayProxy.php new file mode 100644 index 0000000000000..d458062163ada --- /dev/null +++ b/src/Symfony/Component/Cache/Traits/RelayProxy.php @@ -0,0 +1,1262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Relay\Relay; +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RelayProxy extends Relay implements ResetInterface, LazyObjectInterface +{ + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = [ + 'lazyObjectReal' => [self::class, 'lazyObjectReal', null], + "\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null], + ]; + + public function __construct($host = null, $port = 6379, $connect_timeout = 0.0, $command_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0) + { + return $this->lazyObjectReal->__construct(...\func_get_args()); + } + + public function connect($host, $port = 6379, $timeout = 0.0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0): bool + { + return $this->lazyObjectReal->connect(...\func_get_args()); + } + + public function pconnect($host, $port = 6379, $timeout = 0.0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0): bool + { + return $this->lazyObjectReal->pconnect(...\func_get_args()); + } + + public function close(): bool + { + return $this->lazyObjectReal->close(...\func_get_args()); + } + + public function pclose(): bool + { + return $this->lazyObjectReal->pclose(...\func_get_args()); + } + + public function listen($callback): bool + { + return $this->lazyObjectReal->listen(...\func_get_args()); + } + + public function onFlushed($callback): bool + { + return $this->lazyObjectReal->onFlushed(...\func_get_args()); + } + + public function onInvalidated($callback, $pattern = null): bool + { + return $this->lazyObjectReal->onInvalidated(...\func_get_args()); + } + + public function dispatchEvents(): false|int + { + return $this->lazyObjectReal->dispatchEvents(...\func_get_args()); + } + + public function getOption($option): mixed + { + return $this->lazyObjectReal->getOption(...\func_get_args()); + } + + public function option($option, $value = null): mixed + { + return $this->lazyObjectReal->option(...\func_get_args()); + } + + public function setOption($option, $value): bool + { + return $this->lazyObjectReal->setOption(...\func_get_args()); + } + + public function getTimeout(): false|float + { + return $this->lazyObjectReal->getTimeout(...\func_get_args()); + } + + public function timeout(): false|float + { + return $this->lazyObjectReal->timeout(...\func_get_args()); + } + + public function getReadTimeout(): false|float + { + return $this->lazyObjectReal->getReadTimeout(...\func_get_args()); + } + + public function readTimeout(): false|float + { + return $this->lazyObjectReal->readTimeout(...\func_get_args()); + } + + public function getBytes(): array + { + return $this->lazyObjectReal->getBytes(...\func_get_args()); + } + + public function bytes(): array + { + return $this->lazyObjectReal->bytes(...\func_get_args()); + } + + public function getHost(): false|string + { + return $this->lazyObjectReal->getHost(...\func_get_args()); + } + + public function isConnected(): bool + { + return $this->lazyObjectReal->isConnected(...\func_get_args()); + } + + public function getPort(): false|int + { + return $this->lazyObjectReal->getPort(...\func_get_args()); + } + + public function getAuth(): mixed + { + return $this->lazyObjectReal->getAuth(...\func_get_args()); + } + + public function getDbNum(): mixed + { + return $this->lazyObjectReal->getDbNum(...\func_get_args()); + } + + public function _serialize($value): mixed + { + return $this->lazyObjectReal->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return $this->lazyObjectReal->_unserialize(...\func_get_args()); + } + + public function _compress($value): string + { + return $this->lazyObjectReal->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return $this->lazyObjectReal->_uncompress(...\func_get_args()); + } + + public function _pack($value): string + { + return $this->lazyObjectReal->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return $this->lazyObjectReal->_unpack(...\func_get_args()); + } + + public function _prefix($value): string + { + return $this->lazyObjectReal->_prefix(...\func_get_args()); + } + + public function getLastError(): ?string + { + return $this->lazyObjectReal->getLastError(...\func_get_args()); + } + + public function clearLastError(): bool + { + return $this->lazyObjectReal->clearLastError(...\func_get_args()); + } + + public function endpointId(): false|string + { + return $this->lazyObjectReal->endpointId(...\func_get_args()); + } + + public function getPersistentID(): false|string + { + return $this->lazyObjectReal->getPersistentID(...\func_get_args()); + } + + public function socketId(): false|string + { + return $this->lazyObjectReal->socketId(...\func_get_args()); + } + + public function rawCommand($cmd, ...$args): mixed + { + return $this->lazyObjectReal->rawCommand(...\func_get_args()); + } + + public function select($db): \Relay\Relay|bool + { + return $this->lazyObjectReal->select(...\func_get_args()); + } + + public function auth(#[\SensitiveParameter] $auth): bool + { + return $this->lazyObjectReal->auth(...\func_get_args()); + } + + public function info(...$sections): \Relay\Relay|array|false + { + return $this->lazyObjectReal->info(...\func_get_args()); + } + + public function flushdb($async = false): \Relay\Relay|bool + { + return $this->lazyObjectReal->flushdb(...\func_get_args()); + } + + public function flushall($async = false): \Relay\Relay|bool + { + return $this->lazyObjectReal->flushall(...\func_get_args()); + } + + public function fcall($name, $argv = [], $keys = [], $handler = null): mixed + { + return $this->lazyObjectReal->fcall(...\func_get_args()); + } + + public function fcall_ro($name, $argv = [], $keys = [], $handler = null): mixed + { + return $this->lazyObjectReal->fcall_ro(...\func_get_args()); + } + + public function function($op, ...$args): mixed + { + return $this->lazyObjectReal->function(...\func_get_args()); + } + + public function dbsize(): \Relay\Relay|false|int + { + return $this->lazyObjectReal->dbsize(...\func_get_args()); + } + + public function dump($key): \Relay\Relay|false|string + { + return $this->lazyObjectReal->dump(...\func_get_args()); + } + + public function replicaof($host = null, $port = 0): \Relay\Relay|bool + { + return $this->lazyObjectReal->replicaof(...\func_get_args()); + } + + public function restore($key, $ttl, $value, $options = null): \Relay\Relay|bool + { + return $this->lazyObjectReal->restore(...\func_get_args()); + } + + public function migrate($host, $port, $key, $dstdb, $timeout, $copy = false, $replace = false, #[\SensitiveParameter] $credentials = null): \Relay\Relay|bool + { + return $this->lazyObjectReal->migrate(...\func_get_args()); + } + + public function copy($src, $dst, $options = null): \Relay\Relay|false|int + { + return $this->lazyObjectReal->copy(...\func_get_args()); + } + + public function echo($arg): \Relay\Relay|bool|string + { + return $this->lazyObjectReal->echo(...\func_get_args()); + } + + public function ping($arg = null): \Relay\Relay|bool|string + { + return $this->lazyObjectReal->ping(...\func_get_args()); + } + + public function idleTime(): \Relay\Relay|false|int + { + return $this->lazyObjectReal->idleTime(...\func_get_args()); + } + + public function randomkey(): \Relay\Relay|bool|null|string + { + return $this->lazyObjectReal->randomkey(...\func_get_args()); + } + + public function time(): \Relay\Relay|array|false + { + return $this->lazyObjectReal->time(...\func_get_args()); + } + + public function bgrewriteaof(): \Relay\Relay|bool + { + return $this->lazyObjectReal->bgrewriteaof(...\func_get_args()); + } + + public function lastsave(): \Relay\Relay|false|int + { + return $this->lazyObjectReal->lastsave(...\func_get_args()); + } + + public function bgsave(): \Relay\Relay|bool + { + return $this->lazyObjectReal->bgsave(...\func_get_args()); + } + + public function save(): \Relay\Relay|bool + { + return $this->lazyObjectReal->save(...\func_get_args()); + } + + public function role(): \Relay\Relay|array|false + { + return $this->lazyObjectReal->role(...\func_get_args()); + } + + public function ttl($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->ttl(...\func_get_args()); + } + + public function pttl($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->pttl(...\func_get_args()); + } + + public function exists(...$keys): \Relay\Relay|bool|int + { + return $this->lazyObjectReal->exists(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return $this->lazyObjectReal->eval(...\func_get_args()); + } + + public function eval_ro($script, $args = [], $num_keys = 0): mixed + { + return $this->lazyObjectReal->eval_ro(...\func_get_args()); + } + + public function evalsha($sha, $args = [], $num_keys = 0): mixed + { + return $this->lazyObjectReal->evalsha(...\func_get_args()); + } + + public function evalsha_ro($sha, $args = [], $num_keys = 0): mixed + { + return $this->lazyObjectReal->evalsha_ro(...\func_get_args()); + } + + public function client($operation, ...$args): mixed + { + return $this->lazyObjectReal->client(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \Relay\Relay|false|int + { + return $this->lazyObjectReal->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null): \Relay\Relay|false|float + { + return $this->lazyObjectReal->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \Relay\Relay|array|false + { + return $this->lazyObjectReal->geohash(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return $this->lazyObjectReal->georadius(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return $this->lazyObjectReal->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return $this->lazyObjectReal->georadiusbymember_ro(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return $this->lazyObjectReal->georadius_ro(...\func_get_args()); + } + + public function geosearch($key, $position, $shape, $unit, $options = []): \Relay\Relay|array + { + return $this->lazyObjectReal->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \Relay\Relay|false|int + { + return $this->lazyObjectReal->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return $this->lazyObjectReal->get(...\func_get_args()); + } + + public function getset($key, $value): mixed + { + return $this->lazyObjectReal->getset(...\func_get_args()); + } + + public function getrange($key, $start, $end): \Relay\Relay|false|string + { + return $this->lazyObjectReal->getrange(...\func_get_args()); + } + + public function setrange($key, $start, $value): \Relay\Relay|false|int + { + return $this->lazyObjectReal->setrange(...\func_get_args()); + } + + public function getbit($key, $pos): \Relay\Relay|false|int + { + return $this->lazyObjectReal->getbit(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $by_bit = false): \Relay\Relay|false|int + { + return $this->lazyObjectReal->bitcount(...\func_get_args()); + } + + public function config($operation, $key = null, $value = null): \Relay\Relay|array|bool + { + return $this->lazyObjectReal->config(...\func_get_args()); + } + + public function command(...$args): \Relay\Relay|array|false|int + { + return $this->lazyObjectReal->command(...\func_get_args()); + } + + public function bitop($operation, $dstkey, $srckey, ...$other_keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null, $bybit = false): \Relay\Relay|false|int + { + return $this->lazyObjectReal->bitpos(...\func_get_args()); + } + + public function setbit($key, $pos, $val): \Relay\Relay|false|int + { + return $this->lazyObjectReal->setbit(...\func_get_args()); + } + + public function acl($cmd, ...$args): mixed + { + return $this->lazyObjectReal->acl(...\func_get_args()); + } + + public function append($key, $value): \Relay\Relay|false|int + { + return $this->lazyObjectReal->append(...\func_get_args()); + } + + public function set($key, $value, $options = null): mixed + { + return $this->lazyObjectReal->set(...\func_get_args()); + } + + public function getex($key, $options = null): mixed + { + return $this->lazyObjectReal->getex(...\func_get_args()); + } + + public function getdel($key): mixed + { + return $this->lazyObjectReal->getdel(...\func_get_args()); + } + + public function setex($key, $seconds, $value): \Relay\Relay|bool + { + return $this->lazyObjectReal->setex(...\func_get_args()); + } + + public function pfadd($key, $elements): \Relay\Relay|false|int + { + return $this->lazyObjectReal->pfadd(...\func_get_args()); + } + + public function pfcount($key): \Relay\Relay|int + { + return $this->lazyObjectReal->pfcount(...\func_get_args()); + } + + public function pfmerge($dst, $srckeys): \Relay\Relay|bool + { + return $this->lazyObjectReal->pfmerge(...\func_get_args()); + } + + public function psetex($key, $milliseconds, $value): \Relay\Relay|bool + { + return $this->lazyObjectReal->psetex(...\func_get_args()); + } + + public function publish($channel, $message): \Relay\Relay|false|int + { + return $this->lazyObjectReal->publish(...\func_get_args()); + } + + public function setnx($key, $value): \Relay\Relay|bool + { + return $this->lazyObjectReal->setnx(...\func_get_args()); + } + + public function mget($keys): \Relay\Relay|array|false + { + return $this->lazyObjectReal->mget(...\func_get_args()); + } + + public function move($key, $db): \Relay\Relay|false|int + { + return $this->lazyObjectReal->move(...\func_get_args()); + } + + public function mset($kvals): \Relay\Relay|bool + { + return $this->lazyObjectReal->mset(...\func_get_args()); + } + + public function msetnx($kvals): \Relay\Relay|bool + { + return $this->lazyObjectReal->msetnx(...\func_get_args()); + } + + public function rename($key, $newkey): \Relay\Relay|bool + { + return $this->lazyObjectReal->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey): \Relay\Relay|bool + { + return $this->lazyObjectReal->renamenx(...\func_get_args()); + } + + public function del(...$keys): \Relay\Relay|bool|int + { + return $this->lazyObjectReal->del(...\func_get_args()); + } + + public function unlink(...$keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->unlink(...\func_get_args()); + } + + public function expire($key, $seconds, $mode = null): \Relay\Relay|bool + { + return $this->lazyObjectReal->expire(...\func_get_args()); + } + + public function pexpire($key, $milliseconds): \Relay\Relay|bool + { + return $this->lazyObjectReal->pexpire(...\func_get_args()); + } + + public function expireat($key, $timestamp): \Relay\Relay|bool + { + return $this->lazyObjectReal->expireat(...\func_get_args()); + } + + public function expiretime($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->expiretime(...\func_get_args()); + } + + public function pexpireat($key, $timestamp_ms): \Relay\Relay|bool + { + return $this->lazyObjectReal->pexpireat(...\func_get_args()); + } + + public function pexpiretime($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->pexpiretime(...\func_get_args()); + } + + public function persist($key): \Relay\Relay|bool + { + return $this->lazyObjectReal->persist(...\func_get_args()); + } + + public function type($key): \Relay\Relay|bool|int|string + { + return $this->lazyObjectReal->type(...\func_get_args()); + } + + public function lmove($srckey, $dstkey, $srcpos, $dstpos): \Relay\Relay|false|null|string + { + return $this->lazyObjectReal->lmove(...\func_get_args()); + } + + public function blmove($srckey, $dstkey, $srcpos, $dstpos, $timeout): \Relay\Relay|false|null|string + { + return $this->lazyObjectReal->blmove(...\func_get_args()); + } + + public function lrange($key, $start, $stop): \Relay\Relay|array|false + { + return $this->lazyObjectReal->lrange(...\func_get_args()); + } + + public function lpush($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->lazyObjectReal->lpush(...\func_get_args()); + } + + public function rpush($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->lazyObjectReal->rpush(...\func_get_args()); + } + + public function lpushx($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->lazyObjectReal->lpushx(...\func_get_args()); + } + + public function rpushx($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->lazyObjectReal->rpushx(...\func_get_args()); + } + + public function lset($key, $index, $mem): \Relay\Relay|bool + { + return $this->lazyObjectReal->lset(...\func_get_args()); + } + + public function lpop($key, $count = 1): mixed + { + return $this->lazyObjectReal->lpop(...\func_get_args()); + } + + public function lpos($key, $value, $options = null): \Relay\Relay|array|false|int|null + { + return $this->lazyObjectReal->lpos(...\func_get_args()); + } + + public function rpop($key, $count = 1): mixed + { + return $this->lazyObjectReal->rpop(...\func_get_args()); + } + + public function rpoplpush($source, $dest): mixed + { + return $this->lazyObjectReal->rpoplpush(...\func_get_args()); + } + + public function brpoplpush($source, $dest, $timeout): mixed + { + return $this->lazyObjectReal->brpoplpush(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->blpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->blmpop(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->bzmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->lmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->zmpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->brpop(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->bzpopmin(...\func_get_args()); + } + + public function object($op, $key): mixed + { + return $this->lazyObjectReal->object(...\func_get_args()); + } + + public function geopos($key, ...$members): \Relay\Relay|array|false + { + return $this->lazyObjectReal->geopos(...\func_get_args()); + } + + public function lrem($key, $mem, $count = 0): \Relay\Relay|false|int + { + return $this->lazyObjectReal->lrem(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return $this->lazyObjectReal->lindex(...\func_get_args()); + } + + public function linsert($key, $op, $pivot, $element): \Relay\Relay|false|int + { + return $this->lazyObjectReal->linsert(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \Relay\Relay|bool + { + return $this->lazyObjectReal->ltrim(...\func_get_args()); + } + + public function hget($hash, $member): mixed + { + return $this->lazyObjectReal->hget(...\func_get_args()); + } + + public function hstrlen($hash, $member): \Relay\Relay|false|int + { + return $this->lazyObjectReal->hstrlen(...\func_get_args()); + } + + public function hgetall($hash): \Relay\Relay|array|false + { + return $this->lazyObjectReal->hgetall(...\func_get_args()); + } + + public function hkeys($hash): \Relay\Relay|array|false + { + return $this->lazyObjectReal->hkeys(...\func_get_args()); + } + + public function hvals($hash): \Relay\Relay|array|false + { + return $this->lazyObjectReal->hvals(...\func_get_args()); + } + + public function hmget($hash, $members): \Relay\Relay|array|false + { + return $this->lazyObjectReal->hmget(...\func_get_args()); + } + + public function hrandfield($hash, $options = null): \Relay\Relay|array|false|string + { + return $this->lazyObjectReal->hrandfield(...\func_get_args()); + } + + public function hmset($hash, $members): \Relay\Relay|bool + { + return $this->lazyObjectReal->hmset(...\func_get_args()); + } + + public function hexists($hash, $member): \Relay\Relay|bool + { + return $this->lazyObjectReal->hexists(...\func_get_args()); + } + + public function hsetnx($hash, $member, $value): \Relay\Relay|bool + { + return $this->lazyObjectReal->hsetnx(...\func_get_args()); + } + + public function hset($key, $mem, $val, ...$kvals): \Relay\Relay|false|int + { + return $this->lazyObjectReal->hset(...\func_get_args()); + } + + public function hdel($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->lazyObjectReal->hdel(...\func_get_args()); + } + + public function hincrby($key, $mem, $value): \Relay\Relay|false|int + { + return $this->lazyObjectReal->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $mem, $value): \Relay\Relay|bool|float + { + return $this->lazyObjectReal->hincrbyfloat(...\func_get_args()); + } + + public function incr($key, $by = 1): \Relay\Relay|false|int + { + return $this->lazyObjectReal->incr(...\func_get_args()); + } + + public function decr($key, $by = 1): \Relay\Relay|false|int + { + return $this->lazyObjectReal->decr(...\func_get_args()); + } + + public function incrby($key, $value): \Relay\Relay|false|int + { + return $this->lazyObjectReal->incrby(...\func_get_args()); + } + + public function decrby($key, $value): \Relay\Relay|false|int + { + return $this->lazyObjectReal->decrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value): \Relay\Relay|false|float + { + return $this->lazyObjectReal->incrbyfloat(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys): \Relay\Relay|array|false + { + return $this->lazyObjectReal->sdiff(...\func_get_args()); + } + + public function sdiffstore($key, ...$other_keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->sdiffstore(...\func_get_args()); + } + + public function sinter($key, ...$other_keys): \Relay\Relay|array|false + { + return $this->lazyObjectReal->sinter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \Relay\Relay|false|int + { + return $this->lazyObjectReal->sintercard(...\func_get_args()); + } + + public function sinterstore($key, ...$other_keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->sinterstore(...\func_get_args()); + } + + public function sunion($key, ...$other_keys): \Relay\Relay|array|false + { + return $this->lazyObjectReal->sunion(...\func_get_args()); + } + + public function sunionstore($key, ...$other_keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->sunionstore(...\func_get_args()); + } + + public function touch($key_or_array, ...$more_keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->touch(...\func_get_args()); + } + + public function pipeline(): \Relay\Relay|bool + { + return $this->lazyObjectReal->pipeline(...\func_get_args()); + } + + public function multi($mode = 0): \Relay\Relay|bool + { + return $this->lazyObjectReal->multi(...\func_get_args()); + } + + public function exec(): \Relay\Relay|array|bool + { + return $this->lazyObjectReal->exec(...\func_get_args()); + } + + public function wait($replicas, $timeout): \Relay\Relay|false|int + { + return $this->lazyObjectReal->wait(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \Relay\Relay|bool + { + return $this->lazyObjectReal->watch(...\func_get_args()); + } + + public function unwatch(): \Relay\Relay|bool + { + return $this->lazyObjectReal->unwatch(...\func_get_args()); + } + + public function discard(): bool + { + return $this->lazyObjectReal->discard(...\func_get_args()); + } + + public function getMode($masked = false): int + { + return $this->lazyObjectReal->getMode(...\func_get_args()); + } + + public function clearBytes(): void + { + $this->lazyObjectReal->clearBytes(...\func_get_args()); + } + + public function scan(&$iterator, $match = null, $count = 0, $type = null): array|false + { + return $this->lazyObjectReal->scan(...\func_get_args()); + } + + public function hscan($key, &$iterator, $match = null, $count = 0): array|false + { + return $this->lazyObjectReal->hscan(...\func_get_args()); + } + + public function sscan($key, &$iterator, $match = null, $count = 0): array|false + { + return $this->lazyObjectReal->sscan(...\func_get_args()); + } + + public function zscan($key, &$iterator, $match = null, $count = 0): array|false + { + return $this->lazyObjectReal->zscan(...\func_get_args()); + } + + public function keys($pattern): \Relay\Relay|array|false + { + return $this->lazyObjectReal->keys(...\func_get_args()); + } + + public function slowlog($operation, ...$extra_args): \Relay\Relay|array|bool|int + { + return $this->lazyObjectReal->slowlog(...\func_get_args()); + } + + public function smembers($set): \Relay\Relay|array|false + { + return $this->lazyObjectReal->smembers(...\func_get_args()); + } + + public function sismember($set, $member): \Relay\Relay|bool + { + return $this->lazyObjectReal->sismember(...\func_get_args()); + } + + public function smismember($set, ...$members): \Relay\Relay|array|false + { + return $this->lazyObjectReal->smismember(...\func_get_args()); + } + + public function srem($set, $member, ...$members): \Relay\Relay|false|int + { + return $this->lazyObjectReal->srem(...\func_get_args()); + } + + public function sadd($set, $member, ...$members): \Relay\Relay|false|int + { + return $this->lazyObjectReal->sadd(...\func_get_args()); + } + + public function sort($key, $options = []): \Relay\Relay|array|false|int + { + return $this->lazyObjectReal->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = []): \Relay\Relay|array|false + { + return $this->lazyObjectReal->sort_ro(...\func_get_args()); + } + + public function smove($srcset, $dstset, $member): \Relay\Relay|bool + { + return $this->lazyObjectReal->smove(...\func_get_args()); + } + + public function spop($set, $count = 1): mixed + { + return $this->lazyObjectReal->spop(...\func_get_args()); + } + + public function srandmember($set, $count = 1): mixed + { + return $this->lazyObjectReal->srandmember(...\func_get_args()); + } + + public function scard($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->scard(...\func_get_args()); + } + + public function script($command, ...$args): mixed + { + return $this->lazyObjectReal->script(...\func_get_args()); + } + + public function strlen($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->strlen(...\func_get_args()); + } + + public function hlen($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->hlen(...\func_get_args()); + } + + public function llen($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->llen(...\func_get_args()); + } + + public function xack($key, $group, $ids): \Relay\Relay|false|int + { + return $this->lazyObjectReal->xack(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false, $nomkstream = false): \Relay\Relay|false|string + { + return $this->lazyObjectReal->xadd(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_idle, $ids, $options): \Relay\Relay|array|bool + { + return $this->lazyObjectReal->xclaim(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \Relay\Relay|array|bool + { + return $this->lazyObjectReal->xautoclaim(...\func_get_args()); + } + + public function xlen($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->xlen(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return $this->lazyObjectReal->xgroup(...\func_get_args()); + } + + public function xdel($key, $ids): \Relay\Relay|false|int + { + return $this->lazyObjectReal->xdel(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return $this->lazyObjectReal->xinfo(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null, $idle = 0): \Relay\Relay|array|false + { + return $this->lazyObjectReal->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \Relay\Relay|array|false + { + return $this->lazyObjectReal->xrange(...\func_get_args()); + } + + public function xrevrange($key, $end, $start, $count = -1): \Relay\Relay|array|bool + { + return $this->lazyObjectReal->xrevrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \Relay\Relay|array|bool|null + { + return $this->lazyObjectReal->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \Relay\Relay|array|bool|null + { + return $this->lazyObjectReal->xreadgroup(...\func_get_args()); + } + + public function xtrim($key, $threshold, $approx = false, $minid = false, $limit = -1): \Relay\Relay|false|int + { + return $this->lazyObjectReal->xtrim(...\func_get_args()); + } + + public function zadd($key, ...$args): mixed + { + return $this->lazyObjectReal->zadd(...\func_get_args()); + } + + public function zrandmember($key, $options = null): mixed + { + return $this->lazyObjectReal->zrandmember(...\func_get_args()); + } + + public function zrange($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrange(...\func_get_args()); + } + + public function zrevrange($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrevrange(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrangebyscore(...\func_get_args()); + } + + public function zrevrangebyscore($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrevrangebyscore(...\func_get_args()); + } + + public function zrangestore($dst, $src, $start, $end, $options = null): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zrangestore(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = -1, $count = -1): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrangebylex(...\func_get_args()); + } + + public function zrevrangebylex($key, $max, $min, $offset = -1, $count = -1): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrevrangebylex(...\func_get_args()); + } + + public function zrank($key, $rank): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zrank(...\func_get_args()); + } + + public function zrevrank($key, $rank): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zrevrank(...\func_get_args()); + } + + public function zrem($key, ...$args): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $start, $end): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zremrangebyscore(...\func_get_args()); + } + + public function zcard($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zcard(...\func_get_args()); + } + + public function zcount($key, $min, $max): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zcount(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zdiff(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zdiffstore(...\func_get_args()); + } + + public function zincrby($key, $score, $mem): \Relay\Relay|false|float + { + return $this->lazyObjectReal->zincrby(...\func_get_args()); + } + + public function zlexcount($key, $min, $max): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zlexcount(...\func_get_args()); + } + + public function zmscore($key, ...$mems): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zmscore(...\func_get_args()); + } + + public function zscore($key, $member): \Relay\Relay|false|float + { + return $this->lazyObjectReal->zscore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zinter(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zintercard(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $options = null): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zinterstore(...\func_get_args()); + } + + public function zunion($keys, $weights = null, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zunion(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $options = null): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zunionstore(...\func_get_args()); + } + + public function zpopmin($key, $count = 1): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zpopmin(...\func_get_args()); + } + + public function zpopmax($key, $count = 1): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zpopmax(...\func_get_args()); + } + + public function _getKeys() + { + return $this->lazyObjectReal->_getKeys(...\func_get_args()); + } +} From a4c2ca83bc47a6811b501fc203e31dd4a83d16ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Wed, 14 Dec 2022 19:58:03 +0100 Subject: [PATCH 152/475] [Lock] Accept Relay connection --- src/Symfony/Component/Lock/CHANGELOG.md | 1 + .../Component/Lock/Store/RedisStore.php | 5 ++- .../Component/Lock/Store/StoreFactory.php | 2 + .../Tests/Store/AbstractRedisStoreTest.php | 7 ++-- .../Lock/Tests/Store/RelayStoreTest.php | 41 +++++++++++++++++++ 5 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/Lock/Tests/Store/RelayStoreTest.php diff --git a/src/Symfony/Component/Lock/CHANGELOG.md b/src/Symfony/Component/Lock/CHANGELOG.md index cc5528ffe188a..d5e1103f9e00e 100644 --- a/src/Symfony/Component/Lock/CHANGELOG.md +++ b/src/Symfony/Component/Lock/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Create migration for lock table when DoctrineDbalStore is used + * Add support for Relay PHP extension for Redis 6.0 --- diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php index 3b3267a5acb6c..4b29e0f1996ab 100644 --- a/src/Symfony/Component/Lock/Store/RedisStore.php +++ b/src/Symfony/Component/Lock/Store/RedisStore.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Lock\Store; use Predis\Response\ServerException; +use Relay\Relay; use Symfony\Component\Lock\Exception\InvalidTtlException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Exception\LockStorageException; @@ -35,7 +36,7 @@ class RedisStore implements SharedLockStoreInterface * @param float $initialTtl The expiration delay of locks in seconds */ public function __construct( - private \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, private float $initialTtl = 300.0, ) { if ($initialTtl <= 0) { @@ -226,7 +227,7 @@ public function exists(Key $key): bool private function evaluate(string $script, string $resource, array $args): mixed { - if ($this->redis instanceof \Redis || $this->redis instanceof \RedisCluster) { + if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof \RedisCluster) { $this->redis->clearLastError(); $result = $this->redis->eval($script, array_merge([$resource], $args), 1); if (null !== $err = $this->redis->getLastError()) { diff --git a/src/Symfony/Component/Lock/Store/StoreFactory.php b/src/Symfony/Component/Lock/Store/StoreFactory.php index f93dcd086c363..7e962ed55da18 100644 --- a/src/Symfony/Component/Lock/Store/StoreFactory.php +++ b/src/Symfony/Component/Lock/Store/StoreFactory.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Lock\Store; use Doctrine\DBAL\Connection; +use Relay\Relay; use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\PersistingStoreInterface; @@ -27,6 +28,7 @@ public static function createStore(#[\SensitiveParameter] object|string $connect { switch (true) { case $connection instanceof \Redis: + case $connection instanceof Relay: case $connection instanceof \RedisArray: case $connection instanceof \RedisCluster: case $connection instanceof \Predis\ClientInterface: diff --git a/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTest.php index 08e5280ad13a9..19563bd24ea8f 100644 --- a/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Lock\Tests\Store; +use Relay\Relay; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; @@ -29,7 +30,7 @@ protected function getClockDelay() return 250000; } - abstract protected function getRedisConnection(): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface; + abstract protected function getRedisConnection(): \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface; public function getStore(): PersistingStoreInterface { @@ -85,7 +86,7 @@ public function exists(Key $key) private function evaluate(string $script, string $resource, array $args) { - if ($this->redis instanceof \Redis || $this->redis instanceof \RedisCluster) { + if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof \RedisCluster) { return $this->redis->eval($script, array_merge([$resource], $args), 1); } @@ -97,7 +98,7 @@ private function evaluate(string $script, string $resource, array $args) return $this->redis->eval(...array_merge([$script, 1, $resource], $args)); } - throw new InvalidArgumentException(sprintf('"%s()" expects being initialized with a Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($this->redis))); + throw new InvalidArgumentException(sprintf('"%s()" expects being initialized with a Redis, Relay, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($this->redis))); } private function getUniqueToken(Key $key): string diff --git a/src/Symfony/Component/Lock/Tests/Store/RelayStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/RelayStoreTest.php new file mode 100644 index 0000000000000..1c6657a5d7159 --- /dev/null +++ b/src/Symfony/Component/Lock/Tests/Store/RelayStoreTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Store; + +use PHPUnit\Framework\SkippedTestSuiteError; +use Relay\Relay; +use Symfony\Component\Lock\Tests\Store\AbstractRedisStoreTest; +use Symfony\Component\Lock\Tests\Store\SharedLockStoreTestTrait; + +/** + * @requires extension relay + * + * @group integration + */ +class RelayStoreTest extends AbstractRedisStoreTest +{ + use SharedLockStoreTestTrait; + + public static function setUpBeforeClass(): void + { + try { + new Relay(...explode(':', getenv('REDIS_HOST'))); + } catch (\Relay\Exception $e) { + throw new SkippedTestSuiteError($e->getMessage()); + } + } + + protected function getRedisConnection(): Relay + { + return new Relay(...explode(':', getenv('REDIS_HOST'))); + } +} From 0366a32a5d5c33a7cc25f7f287a6b1ba737a55ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Wed, 14 Dec 2022 20:24:34 +0100 Subject: [PATCH 153/475] [HttpFoundation] Accept Relay connection --- .../Component/HttpFoundation/CHANGELOG.md | 1 + .../Storage/Handler/RedisSessionHandler.php | 3 +- .../Storage/Handler/SessionHandlerFactory.php | 2 ++ .../AbstractRedisSessionHandlerTestCase.php | 4 ++- .../Handler/RelaySessionHandlerTest.php | 28 +++++++++++++++++++ 5 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 208f466d749b8..8e70070fc8723 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `ParameterBag::getEnum()` * Create migration for session table when pdo handler is used + * Add support for Relay PHP extension for Redis 6.2 --- diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php index c4f1e0216e6cf..b696eee4b7d1f 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; use Predis\Response\ErrorInterface; +use Relay\Relay; /** * Redis based session storage handler based on the Redis class @@ -39,7 +40,7 @@ class RedisSessionHandler extends AbstractSessionHandler * @throws \InvalidArgumentException When unsupported client or options are passed */ public function __construct( - private \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, array $options = [], ) { if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php index 33aaa7df5f60a..dbbe7dc880e23 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; use Doctrine\DBAL\DriverManager; +use Relay\Relay; use Symfony\Component\Cache\Adapter\AbstractAdapter; /** @@ -32,6 +33,7 @@ public static function createHandler(object|string $connection, array $options = switch (true) { case $connection instanceof \Redis: + case $connection instanceof Relay: case $connection instanceof \RedisArray: case $connection instanceof \RedisCluster: case $connection instanceof \Predis\ClientInterface: diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php index e5937b7df6494..e0c030310709d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php @@ -12,10 +12,12 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; use PHPUnit\Framework\TestCase; +use Relay\Relay; use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler; /** * @requires extension redis + * * @group time-sensitive */ abstract class AbstractRedisSessionHandlerTestCase extends TestCase @@ -32,7 +34,7 @@ abstract class AbstractRedisSessionHandlerTestCase extends TestCase */ protected $redisClient; - abstract protected function createRedisClient(string $host): \Redis|\RedisArray|\RedisCluster|\Predis\Client; + abstract protected function createRedisClient(string $host): \Redis|Relay|\RedisArray|\RedisCluster|\Predis\Client; protected function setUp(): void { diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php new file mode 100 10000 644 index 0000000000000..76553f96d3375 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Session\Storage\Handler; + +use Relay\Relay; +use Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler\AbstractRedisSessionHandlerTestCase; + +/** + * @requires extension relay + * + * @group integration + */ +class RelaySessionHandlerTest extends AbstractRedisSessionHandlerTestCase +{ + protected function createRedisClient(string $host): Relay + { + return new Relay(...explode(':', $host)); + } +} From 1d7b4093991257f7ece5b1abc18f7b5f3de9e098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Wed, 14 Dec 2022 20:44:00 +0100 Subject: [PATCH 154/475] [Semaphore] Accept Relay connection --- src/Symfony/Component/Semaphore/CHANGELOG.md | 5 +++ .../Component/Semaphore/Store/RedisStore.php | 5 ++- .../Semaphore/Store/StoreFactory.php | 3 +- .../Tests/Store/AbstractRedisStoreTest.php | 3 +- .../Semaphore/Tests/Store/RelayStoreTest.php | 40 +++++++++++++++++++ 5 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Semaphore/Tests/Store/RelayStoreTest.php diff --git a/src/Symfony/Component/Semaphore/CHANGELOG.md b/src/Symfony/Component/Semaphore/CHANGELOG.md index f53fc9dc2a2cf..c92b74aba72ae 100644 --- a/src/Symfony/Component/Semaphore/CHANGELOG.md +++ b/src/Symfony/Component/Semaphore/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add support for Relay PHP extension for Redis + 5.3 --- diff --git a/src/Symfony/Component/Semaphore/Store/RedisStore.php b/src/Symfony/Component/Semaphore/Store/RedisStore.php index 0b3652c5d9c11..f1c6fcc30edd9 100644 --- a/src/Symfony/Component/Semaphore/Store/RedisStore.php +++ b/src/Symfony/Component/Semaphore/Store/RedisStore.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Semaphore\Store; +use Relay\Relay; use Symfony\Component\Semaphore\Exception\InvalidArgumentException; use Symfony\Component\Semaphore\Exception\SemaphoreAcquiringException; use Symfony\Component\Semaphore\Exception\SemaphoreExpiredException; @@ -26,7 +27,7 @@ class RedisStore implements PersistingStoreInterface { public function __construct( - private \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, ) { } @@ -157,7 +158,7 @@ public function exists(Key $key): bool private function evaluate(string $script, string $resource, array $args): mixed { - if ($this->redis instanceof \Redis || $this->redis instanceof \RedisCluster) { + if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof \RedisCluster) { return $this->redis->eval($script, array_merge([$resource], $args), 1); } diff --git a/src/Symfony/Component/Semaphore/Store/StoreFactory.php b/src/Symfony/Component/Semaphore/Store/StoreFactory.php index eb77431c39030..639298bab2453 100644 --- a/src/Symfony/Component/Semaphore/Store/StoreFactory.php +++ b/src/Symfony/Component/Semaphore/Store/StoreFactory.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Semaphore\Store; +use Relay\Relay; use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\Semaphore\Exception\InvalidArgumentException; use Symfony\Component\Semaphore\PersistingStoreInterface; @@ -26,6 +27,7 @@ public static function createStore(#[\SensitiveParameter] object|string $connect { switch (true) { case $connection instanceof \Redis: + case $connection instanceof Relay: case $connection instanceof \RedisArray: case $connection instanceof \RedisCluster: case $connection instanceof \Predis\ClientInterface: @@ -33,7 +35,6 @@ public static function createStore(#[\SensitiveParameter] object|string $connect case !\is_string($connection): throw new InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection::class)); - case str_starts_with($connection, 'redis://'): case str_starts_with($connection, 'rediss://'): if (!class_exists(AbstractAdapter::class)) { diff --git a/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTest.php b/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTest.php index 4952f880f6a32..a7de9357a2373 100644 --- a/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTest.php +++ b/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Semaphore\Tests\Store; +use Relay\Relay; use Symfony\Component\Semaphore\PersistingStoreInterface; use Symfony\Component\Semaphore\Store\RedisStore; @@ -19,7 +20,7 @@ */ abstract class AbstractRedisStoreTest extends AbstractStoreTest { - abstract protected function getRedisConnection(): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface; + abstract protected function getRedisConnection(): \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface; public function getStore(): PersistingStoreInterface { diff --git a/src/Symfony/Component/Semaphore/Tests/Store/RelayStoreTest.php b/src/Symfony/Component/Semaphore/Tests/Store/RelayStoreTest.php new file mode 100644 index 0000000000000..694e0b4b7b983 --- /dev/null +++ b/src/Symfony/Component/Semaphore/Tests/Store/RelayStoreTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Semaphore\Tests\Store; + +use PHPUnit\Framework\SkippedTestSuiteError; +use Relay\Relay; + +/** + * @requires extension relay + */ +class RelayStoreTest extends AbstractRedisStoreTest +{ + protected function setUp(): void + { + $this->getRedisConnection()->flushDB(); + } + + public static function setUpBeforeClass(): void + { + try { + new Relay(...explode(':', getenv('REDIS_HOST'))); + } catch (\Relay\Exception $e) { + throw new SkippedTestSuiteError($e->getMessage()); + } + } + + protected function getRedisConnection(): Relay + { + return new Relay(...explode(':', getenv('REDIS_HOST'))); + } +} From 327969e6b190106022b2dc08ff6b994c4696bc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Sun, 18 Dec 2022 02:01:04 +0100 Subject: [PATCH 155/475] [redis-messenger] Add Relay support --- .../Messenger/Bridge/Redis/CHANGELOG.md | 5 +++ .../Transport/RedisExtIntegrationTest.php | 17 +++++--- .../Transport/RelayExtIntegrationTest.php | 28 ++++++++++++ .../Bridge/Redis/Transport/Connection.php | 43 ++++++++++--------- 4 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RelayExtIntegrationTest.php diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md b/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md index 410e6eeaee302..640ed7f9a8515 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add support for Relay PHP extension for Redis + 6.1 --- diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php index 5ac0dbb5fc668..4117b14785b3c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php @@ -12,12 +12,14 @@ namespace Symfony\Component\Messenger\Bridge\Redis\Tests\Transport; use PHPUnit\Framework\TestCase; +use Relay\Relay; use Symfony\Component\Messenger\Bridge\Redis\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Bridge\Redis\Transport\Connection; use Symfony\Component\Messenger\Exception\TransportException; /** * @requires extension redis + * * @group time-sensitive * @group integration */ @@ -258,7 +260,7 @@ public function testLazyCluster() public function testLazy() { - $redis = new \Redis(); + $redis = $this->createRedisClient(); $connection = Connection::fromDsn('redis://localhost/messenger-lazy?lazy=1', [], $redis); $connection->add('1', []); @@ -275,7 +277,7 @@ public function testLazy() public function testDbIndex() { - $redis = new \Redis(); + $redis = $this->createRedisClient(); Connection::fromDsn('redis://localhost/queue?dbindex=2', [], $redis); @@ -296,7 +298,7 @@ public function testFromDsnWithMultipleHosts() public function testJsonError() { - $redis = new \Redis(); + $redis = $this->createRedisClient(); $connection = Connection::fromDsn('redis://localhost/json-error', [], $redis); try { $connection->add("\xB1\x31", []); @@ -308,7 +310,7 @@ public function testJsonError() public function testGetNonBlocking() { - $redis = new \Redis(); + $redis = $this->createRedisClient(); $connection = Connection::fromDsn('redis://localhost/messenger-getnonblocking', ['sentinel_master' => null], $redis); @@ -321,7 +323,7 @@ public function testGetNonBlocking() public function testGetAfterReject() { - $redis = new \Redis(); + $redis = $this->createRedisClient(); $connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['sentinel_master' => null], $redis); $connection->add('1', []); @@ -380,4 +382,9 @@ private function skipIfRedisClusterUnavailable() self::markTestSkipped($e->getMessage()); } } + + protected function createRedisClient(): \Redis|Relay + { + return new \Redis(); + } } diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RelayExtIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RelayExtIntegrationTest.php new file mode 100644 index 0000000000000..bd33434a064c8 --- /dev/null +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RelayExtIntegrationTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Bridge\Redis\Tests\Transport; + +use Relay\Relay; + +/** + * @requires extension relay + * + * @group time-sensitive + * @group integration + */ +class RelayExtIntegrationTest extends RedisExtIntegrationTest +{ + protected function createRedisClient(): \Redis|Relay + { + return new Relay(); + } +} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php index 481dc5d64bba8..837480de209f4 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Messenger\Bridge\Redis\Transport; +use Relay\Relay; +use Relay\Sentinel; use Symfony\Component\Messenger\Exception\InvalidArgumentException; use Symfony\Component\Messenger\Exception\LogicException; use Symfony\Component\Messenger\Exception\TransportException; @@ -43,7 +45,7 @@ class Connection 'claim_interval' => 60000, // Interval by which pending/abandoned messages should be checked 'lazy' => false, 'auth' => null, - 'serializer' => \Redis::SERIALIZER_PHP, + 'serializer' => 1, // see \Redis::SERIALIZER_PHP, 'sentinel_master' => null, // String, master to look for (optional, default is NULL meaning Sentinel support is disabled) 'timeout' => 0.0, // Float, value in seconds (optional, default is 0 meaning unlimited) 'read_timeout' => 0.0, // Float, value in seconds (optional, default is 0 meaning unlimited) @@ -52,7 +54,7 @@ class Connection 'ssl' => null, // see https://php.net/context.ssl ]; - private \Redis|\RedisCluster|\Closure $redis; + private \Redis|Relay|\RedisCluster|\Closure $redis; private string $stream; private string $queue; private string $group; @@ -66,7 +68,7 @@ class Connection private bool $deleteAfterReject; private bool $couldHavePendingMessages = true; - public function __construct(array $options, \Redis|\RedisCluster $redis = null) + public function __construct(array $options, \Redis|Relay|\RedisCluster $redis = null) { if (version_compare(phpversion('redis'), '4.3.0', '<')) { throw new LogicException('The redis transport requires php-redis 4.3.0 or higher.'); @@ -78,8 +80,8 @@ public function __construct(array $options, \Redis|\RedisCluster $redis = null) $auth = $options['auth']; $sentinelMaster = $options['sentinel_master']; - if (null !== $sentinelMaster && !class_exists(\RedisSentinel::class)) { - throw new InvalidArgumentException('Redis Sentinel support requires the "redis" extension v5.2 or higher.'); + if (null !== $sentinelMaster && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + throw new InvalidArgumentException('Redis Sentinel support requires ext-redis>=5.2, or ext-relay.'); } if (null !== $sentinelMaster && ($redis instanceof \RedisCluster || \is_array($host))) { @@ -91,7 +93,8 @@ public function __construct(array $options, \Redis|\RedisCluster $redis = null) $this->redis = static fn () => self::initializeRedisCluster($redis, $hosts, $auth, $options); } else { if (null !== $sentinelMaster) { - $sentinelClient = new \RedisSentinel($host, $port, $options['timeout'], $options['persistent_id'], $options['retry_interval'], $options['read_timeout']); + $sentinelClass = \extension_loaded('redis') ? \RedisSentinel::class : Sentinel::class; + $sentinelClient = new $sentinelClass($host, $port, $options['timeout'], $options['persistent_id'], $options['retry_interval'], $options['read_timeout']); if (!$address = $sentinelClient->getMasterAddrByName($sentinelMaster)) { throw new InvalidArgumentException(sprintf('Failed to retrieve master information from master name "%s" and address "%s:%d".', $sentinelMaster, $host, $port)); @@ -100,7 +103,7 @@ public function __construct(array $options, \Redis|\RedisCluster $redis = null) [$host, $port] = $address; } - $this->redis = static fn () => self::initializeRedis($redis ?? new \Redis(), $host, $port, $auth, $options); + $this->redis = static fn () => self::initializeRedis($redis ?? (\extension_loaded('redis') ? new \Redis() : new Relay()), $host, $port, $auth, $options); } if (!$options['lazy']) { @@ -128,12 +131,12 @@ public function __construct(array $options, \Redis|\RedisCluster $redis = null) /** * @param string|string[]|null $auth */ - private static function initializeRedis(\Redis $redis, string $host, int $port, string|array|null $auth, array $params): \Redis + private static function initializeRedis(\Redis|Relay $redis, string $host, int $port, string|array|null $auth, array $params): \Redis|Relay { $connect = isset($params['persistent_id']) ? 'pconnect' : 'connect'; - $redis->{$connect}($host, $port, $params['timeout'], $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [['stream' => $params['ssl'] ?? null]] : []); + $redis->{$connect}($host, $port, $params['timeout'], $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...(\defined('Redis::SCAN_PREFIX') || \extension_loaded('relay')) ? [['stream' => $params['ssl'] ?? null]] : []); - $redis->setOption(\Redis::OPT_SERIALIZER, $params['serializer']); + $redis->setOption($redis instanceof \Redis ? \Redis::OPT_SERIALIZER : Relay::OPT_SERIALIZER, $params['serializer']); if (null !== $auth && !$redis->auth($auth)) { throw new InvalidArgumentException('Redis connection failed: '.$redis->getLastError()); @@ -157,7 +160,7 @@ private static function initializeRedisCluster(?\RedisCluster $redis, array $hos return $redis; } - public static function fromDsn(#[\SensitiveParameter] string $dsn, array $options = [], \Redis|\RedisCluster $redis = null): self + public static function fromDsn(#[\SensitiveParameter] string $dsn, array $options = [], \Redis|Relay|\RedisCluster $redis = null): self { if (!str_contains($dsn, ',')) { $parsedUrl = self::parseDsn($dsn, $options); @@ -265,7 +268,7 @@ private function claimOldPendingMessages() // This could soon be optimized with https://github.com/antirez/redis/issues/5212 or // https://github.com/antirez/redis/issues/6256 $pendingMessages = $this->getRedis()->xpending($this->stream, $this->group, '-', '+', 1); - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -294,7 +297,7 @@ private function claimOldPendingMessages() ); $this->couldHavePendingMessages = true; - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } } @@ -352,7 +355,7 @@ public function get(): ?array [$this->stream => $messageId], 1 ); - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -390,7 +393,7 @@ public function ack(string $id): void if ($this->deleteAfterAck) { $acknowledged = $redis->xdel($this->stream, [$id]); } - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -411,7 +414,7 @@ public function reject(string $id): void if ($this->deleteAfterReject) { $deleted = $redis->xdel($this->stream, [$id]) && $deleted; } - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -474,7 +477,7 @@ public function add(string $body, array $headers, int $delayInMs = 0): string $id = $added; } - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { if ($error = $redis->getLastError() ?: null) { $redis->clearLastError(); } @@ -497,7 +500,7 @@ public function setup(): void try { $redis->xgroup('CREATE', $this->stream, $this->group, 0, true); - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -600,7 +603,7 @@ private function rawCommand(string $command, ...$arguments): mixed } else { $result = $redis->rawCommand($command, $this->queue, ...$arguments); } - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -614,7 +617,7 @@ private function rawCommand(string $command, ...$arguments): mixed return $result; } - private function getRedis(): \Redis|\RedisCluster + private function getRedis(): \Redis|Relay|\RedisCluster { if ($this->redis instanceof \Closure) { $this->redis = ($this->redis)(); From c1e5035163ff580207e5f1b3db1c743b4964efbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Sun, 18 Dec 2022 15:47:23 +0100 Subject: [PATCH 156/475] [VarDumper] Add Relay support --- src/Symfony/Component/VarDumper/CHANGELOG.md | 1 + .../VarDumper/Caster/RedisCaster.php | 23 ++++++++++--------- .../VarDumper/Cloner/AbstractCloner.php | 1 + .../Tests/Caster/RedisCasterTest.php | 22 +++++++++++++----- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/Symfony/Component/VarDumper/CHANGELOG.md b/src/Symfony/Component/VarDumper/CHANGELOG.md index c5abeee346a06..3b29f419dc32d 100644 --- a/src/Symfony/Component/VarDumper/CHANGELOG.md +++ b/src/Symfony/Component/VarDumper/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add caster for `WeakMap` * Add support of named arguments to `dd()` and `dump()` to display the argument name + * Add support for `Relay\Relay` 6.2 --- diff --git a/src/Symfony/Component/VarDumper/Caster/RedisCaster.php b/src/Symfony/Component/VarDumper/Caster/RedisCaster.php index eac25a12a8b56..f88c72a40cf86 100644 --- a/src/Symfony/Component/VarDumper/Caster/RedisCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/RedisCaster.php @@ -11,6 +11,7 @@ namespace Symfony\Component\VarDumper\Caster; +use Relay\Relay; use Symfony\Component\VarDumper\Cloner\Stub; /** @@ -23,15 +24,15 @@ class RedisCaster { private const SERIALIZERS = [ - \Redis::SERIALIZER_NONE => 'NONE', - \Redis::SERIALIZER_PHP => 'PHP', + 0 => 'NONE', // Redis::SERIALIZER_NONE + 1 => 'PHP', // Redis::SERIALIZER_PHP 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY ]; private const MODES = [ - \Redis::ATOMIC => 'ATOMIC', - \Redis::MULTI => 'MULTI', - \Redis::PIPELINE => 'PIPELINE', + 0 => 'ATOMIC', // Redis::ATOMIC + 1 => 'MULTI', // Redis::MULTI + 2 => 'PIPELINE', // Redis::PIPELINE ]; private const COMPRESSION_MODES = [ @@ -46,7 +47,7 @@ class RedisCaster \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES', ]; - public static function castRedis(\Redis $c, array $a, Stub $stub, bool $isNested) + public static function castRedis(\Redis|Relay $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; @@ -102,9 +103,9 @@ public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, return $a; } - private static function getRedisOptions(\Redis|\RedisArray|\RedisCluster $redis, array $options = []): EnumStub + private static function getRedisOptions(\Redis|Relay|\RedisArray|\RedisCluster $redis, array $options = []): EnumStub { - $serializer = $redis->getOption(\Redis::OPT_SERIALIZER); + $serializer = $redis->getOption(\defined('Redis::OPT_SERIALIZER') ? \Redis::OPT_SERIALIZER : 1); if (\is_array($serializer)) { foreach ($serializer as &$v) { if (isset(self::SERIALIZERS[$v])) { @@ -136,11 +137,11 @@ private static function getRedisOptions(\Redis|\RedisArray|\RedisCluster $redis, } $options += [ - 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0, - 'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT), + 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : Relay::OPT_TCP_KEEPALIVE, + 'READ_TIMEOUT' => $redis->getOption(\defined('Redis::OPT_READ_TIMEOUT') ? \Redis::OPT_READ_TIMEOUT : Relay::OPT_READ_TIMEOUT), 'COMPRESSION' => $compression, 'SERIALIZER' => $serializer, - 'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX), + 'PREFIX' => $redis->getOption(\defined('Redis::OPT_PREFIX') ? \Redis::OPT_PREFIX : Relay::OPT_PREFIX), 'SCAN' => $retry, ]; diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index bcd3013716e9e..599c59ecf6e54 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -130,6 +130,7 @@ abstract class AbstractCloner implements ClonerInterface 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], + 'Relay\Relay' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'], 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'], diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php index 058b95d0d0ab6..3d56584a5640e 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php @@ -16,13 +16,15 @@ /** * @author Nicolas Grekas - * @requires extension redis * @group integration */ class RedisCasterTest extends TestCase { use VarDumperTestTrait; + /** + * @requires extension redis + */ public function testNotConnected() { $redis = new \Redis(); @@ -36,10 +38,18 @@ public function testNotConnected() $this->assertDumpMatchesFormat($xCast, $redis); } - public function testConnected() + /** + * @testWith ["Redis"] + * ["Relay\\Relay"] + */ + public function testConnected(string $class) { + if (!class_exists($class)) { + self::markTestSkipped(sprintf('"%s" class required', $class)); + } + $redisHost = explode(':', getenv('REDIS_HOST')) + [1 => 6379]; - $redis = new \Redis(); + $redis = new $class; try { $redis->connect(...$redisHost); } catch (\Exception $e) { @@ -47,7 +57,7 @@ public function testConnected() } $xCast = << Date: Thu, 19 Jan 2023 13:18:20 +0100 Subject: [PATCH 157/475] Introduce stub for Voter --- .../Security/Core/Authorization/Voter/Voter.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php index 6ac75993101ff..1f76a42eaf1b8 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php @@ -18,6 +18,9 @@ * * @author Roman Marintšenko * @author Grégoire Pineau + * + * @template TAttribute of string + * @template TSubject of mixed */ abstract class Voter implements VoterInterface, CacheableVoterInterface { @@ -74,13 +77,19 @@ public function supportsType(string $subjectType): bool /** * Determines if the attribute and subject are supported by this voter. * - * @param $subject The subject to secure, e.g. an object the user wants to access or any other PHP type + * @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type + * + * @psalm-assert-if-true TSubject $subject + * @psalm-assert-if-true TAttribute $attribute */ abstract protected function supports(string $attribute, mixed $subject): bool; /** * Perform a single access check operation on a given attribute, subject and token. * It is safe to assume that $attribute and $subject already passed the "supports()" method check. + * + * @param TAttribute $attribute + * @param TSubject $subject */ abstract protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool; } From 743f95b95779ae0fb5bf54c220f722bbec2a39c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 25 Jan 2023 14:51:48 +0100 Subject: [PATCH 158/475] [FrameworkBundle] Register alias for argument for workflow services with workflow name only --- src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../FrameworkBundle/DependencyInjection/FrameworkExtension.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 3179f5ab526c1..67e0dbe1ceb22 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Deprecate `framework:exceptions` tag, unwrap it and replace `framework:exception` tags' `name` attribute by `class` * Deprecate the `notifier.logger_notification_listener` service, use the `notifier.notification_logger_listener` service instead * Allow setting private services with the test container + * Register alias for argument for workflow services with workflow name only 6.2 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 551f02dc66299..358ec36f10bea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1006,6 +1006,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $container->setDefinition($workflowId, $workflowDefinition); $container->setDefinition(sprintf('%s.definition', $workflowId), $definitionDefinition); $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name.'.'.$type); + $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name); // Validate Workflow if ('state_machine' === $workflow['type']) { From defd51dfafda3da843fb45adb2ed22124128a84d Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Thu, 19 Jan 2023 09:36:02 +0100 Subject: [PATCH 159/475] [Config] Allow enum values in EnumNode --- .../Config/Builder/ConfigBuilderGenerator.php | 2 +- src/Symfony/Component/Config/CHANGELOG.md | 5 +++ .../Definition/Dumper/XmlReferenceDumper.php | 4 +- .../Definition/Dumper/YamlReferenceDumper.php | 2 +- .../Component/Config/Definition/EnumNode.php | 37 +++++++++++++++++-- .../Tests/Builder/Fixtures/PrimitiveTypes.php | 3 +- .../Symfony/Config/PrimitiveTypesConfig.php | 2 +- .../Dumper/XmlReferenceDumperTest.php | 2 +- .../Dumper/YamlReferenceDumperTest.php | 2 +- .../Config/Tests/Definition/EnumNodeTest.php | 21 ++++++++--- .../Configuration/ExampleConfiguration.php | 3 +- .../Config/Tests/Fixtures/TestEnum.php | 10 +++++ .../Config/Tests/Fixtures/TestEnum2.php | 10 +++++ 13 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 src/Symfony/Component/Config/Tests/Fixtures/TestEnum.php create mode 100644 src/Symfony/Component/Config/Tests/Fixtures/TestEnum2.php diff --git a/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php b/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php index d7b8dd04432ab..2f00a99beb02e 100644 --- a/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php +++ b/src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php @@ -426,7 +426,7 @@ private function getComment(BaseNode $node): string } if ($node instanceof EnumNode) { - $comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_unique(array_map(fn ($a) => var_export($a, true), $node->getValues()))))."\n"; + $comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_unique(array_map(fn ($a) => !$a instanceof \UnitEnum ? var_export($a, true) : '\\'.ltrim(var_export($a, true), '\\'), $node->getValues()))))."\n"; } else { $parameterTypes = $this->getParameterTypes($node); $comment .= ' * @param ParamConfigurator|'.implode('|', $parameterTypes).' $value'."\n"; diff --git a/src/Symfony/Component/Config/CHANGELOG.md b/src/Symfony/Component/Config/CHANGELOG.md index 0fac3a8a550c7..094d5abba0637 100644 --- a/src/Symfony/Component/Config/CHANGELOG.md +++ b/src/Symfony/Component/Config/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Allow enum values in `EnumNode` + 6.2 --- diff --git a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php index eb8fce4385e6b..980fb74fe453c 100644 --- a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php +++ b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php @@ -107,7 +107,7 @@ private function writeNode(NodeInterface $node, int $depth = 0, bool $root = fal FloatNode::class, IntegerNode::class => 'numeric value', BooleanNode::class => 'true|false', - EnumNode::class => implode('|', array_unique(array_map('json_encode', $prototype->getValues()))), + EnumNode::class => $prototype->getPermissibleValues('|'), default => 'value', }; } @@ -149,7 +149,7 @@ private function writeNode(NodeInterface $node, int $depth = 0, bool $root = fal } if ($child instanceof EnumNode) { - $comments[] = 'One of '.implode('; ', array_unique(array_map('json_encode', $child->getValues()))); + $comments[] = 'One of '.$child->getPermissibleValues('; '); } if (\count($comments)) { diff --git a/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php b/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php index 04cb38c20b451..053a201065d91 100644 --- a/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php +++ b/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php @@ -98,7 +98,7 @@ private function writeNode(NodeInterface $node, NodeInterface $parentNode = null } } } elseif ($node instanceof EnumNode) { - $comments[] = 'One of '.implode('; ', array_unique(array_map('json_encode', $node->getValues()))); + $comments[] = 'One of '.$node->getPermissibleValues('; '); $default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~'; } elseif (VariableNode::class === $node::class && \is_array($example)) { // If there is an array example, we are sure we dont need to print a default value diff --git a/src/Symfony/Component/Config/Definition/EnumNode.php b/src/Symfony/Component/Config/Definition/EnumNode.php index 771813c70b8a9..4b494d00ace57 100644 --- a/src/Symfony/Component/Config/Definition/EnumNode.php +++ b/src/Symfony/Component/Config/Definition/EnumNode.php @@ -29,8 +29,16 @@ public function __construct(?string $name, NodeInterface $parent = null, array $ } foreach ($values as $value) { - if (null !== $value && !\is_scalar($value)) { - throw new \InvalidArgumentException(sprintf('"%s" only supports scalar or null values, "%s" given.', __CLASS__, get_debug_type($value))); + if (null === $value || \is_scalar($value)) { + continue; + } + + if (!$value instanceof \UnitEnum) { + throw new \InvalidArgumentException(sprintf('"%s" only supports scalar, enum, or null values, "%s" given.', __CLASS__, get_debug_type($value))); + } + + if ($value::class !== ($enumClass ??= $value::class)) { + throw new \InvalidArgumentException(sprintf('"%s" only supports one type of enum, "%s" and "%s" passed.', __CLASS__, $enumClass, $value::class)); } } @@ -43,12 +51,35 @@ public function getValues() return $this->values; } + /** + * @internal + */ + public function getPermissibleValues(string $separator): string + { + return implode($separator, array_unique(array_map(static function (mixed $value): string { + if (!$value instanceof \UnitEnum) { + return json_encode($value); + } + + return ltrim(var_export($value, true), '\\'); + }, $this->values))); + } + + protected function validateType(mixed $value) + { + if ($value instanceof \UnitEnum) { + return; + } + + parent::validateType($value); + } + protected function finalizeValue(mixed $value): mixed { $value = parent::finalizeValue($value); if (!\in_array($value, $this->values, true)) { - $ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), implode(', ', array_unique(array_map('json_encode', $this->values))))); + $ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), $this->getPermissibleValues(', '))); $ex->setPath($this->getPath()); throw $ex; diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.php index 6ffcf5a4ef533..6b0dbd72fc921 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes.php @@ -13,6 +13,7 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Tests\Fixtures\TestEnum; class PrimitiveTypes implements ConfigurationInterface { @@ -23,7 +24,7 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->children() ->booleanNode('boolean_node')->end() - ->enumNode('enum_node')->values(['foo', 'bar', 'baz'])->end() + ->enumNode('enum_node')->values(['foo', 'bar', 'baz', TestEnum::Bar])->end() ->floatNode('float_node')->end() ->integerNode('integer_node')->end() ->scalarNode('scalar_node')->end() diff --git a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes/Symfony/Config/PrimitiveTypesConfig.php b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes/Symfony/Config/PrimitiveTypesConfig.php index 6701d472cec04..b34e0413b8a23 100644 --- a/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes/Symfony/Config/PrimitiveTypesConfig.php +++ b/src/Symfony/Component/Config/Tests/Builder/Fixtures/PrimitiveTypes/Symfony/Config/PrimitiveTypesConfig.php @@ -33,7 +33,7 @@ public function booleanNode($value): static /** * @default null - * @param ParamConfigurator|'foo'|'bar'|'baz' $value + * @param ParamConfigurator|'foo'|'bar'|'baz'|\Symfony\Component\Config\Tests\Fixtures\TestEnum::Bar $value * @return $this */ public function enumNode($value): static diff --git a/src/Symfony/Component/Config/Tests/Definition/Dumper/XmlReferenceDumperTest.php b/src/Symfony/Component/Config/Tests/Definition/Dumper/XmlReferenceDumperTest.php index 520d25666a1c0..e6ce07588f9d0 100644 --- a/src/Symfony/Component/Config/Tests/Definition/Dumper/XmlReferenceDumperTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/Dumper/XmlReferenceDumperTest.php @@ -41,7 +41,7 @@ private function getConfigurationAsString() - + assertSame('foo', $node->finalize('foo')); + $this->assertSame(TestEnum::Bar, $node->finalize(TestEnum::Bar)); } public function testConstructionWithNoValues() @@ -51,8 +54,8 @@ public function testConstructionWithNullName() public function testFinalizeWithInvalidValue() { $this->expectException(InvalidConfigurationException::class); - $this->expectExceptionMessage('The value "foobar" is not allowed for path "foo". Permissible values: "foo", "bar"'); - $node = new EnumNode('foo', null, ['foo', 'bar']); + $this->expectExceptionMessage('The value "foobar" is not allowed for path "foo". Permissible values: "foo", "bar", Symfony\Component\Config\Tests\Fixtures\TestEnum::Foo'); + $node = new EnumNode('foo', null, ['foo', 'bar', TestEnum::Foo]); $node->finalize('foobar'); } @@ -80,11 +83,19 @@ public function testSameStringCoercedValuesAreDifferent() $this->assertNull($node->finalize(null)); } - public function testNonScalarOrNullValueThrows() + public function testNonScalarOrEnumOrNullValueThrows() { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('"Symfony\Component\Config\Definition\EnumNode" only supports scalar or null values, "stdClass" given.'); + $this->expectExceptionMessage('"Symfony\Component\Config\Definition\EnumNode" only supports scalar, enum, or null values, "stdClass" given.'); new EnumNode('ccc', null, [new \stdClass()]); } + + public function testTwoDifferentEnumsThrows() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('"Symfony\Component\Config\Definition\EnumNode" only supports one type of enum, "Symfony\Component\Config\Tests\Fixtures\TestEnum" and "Symfony\Component\Config\Tests\Fixtures\TestEnum2" passed.'); + + new EnumNode('ccc', null, [...TestEnum::cases(), TestEnum2::Ccc]); + } } diff --git a/src/Symfony/Component/Config/Tests/Fixtures/Configuration/ExampleConfiguration.php b/src/Symfony/Component/Config/Tests/Fixtures/Configuration/ExampleConfiguration.php index 126008831796a..bdf6d80bff443 100644 --- a/src/Symfony/Component/Config/Tests/Fixtures/Configuration/ExampleConfiguration.php +++ b/src/Symfony/Component/Config/Tests/Fixtures/Configuration/ExampleConfiguration.php @@ -13,6 +13,7 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Tests\Fixtures\TestEnum; class ExampleConfiguration implements ConfigurationInterface { @@ -38,7 +39,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('scalar_deprecated_with_message')->setDeprecated('vendor/package', '1.1', 'Deprecation custom message for "%node%" at "%path%"')->end() ->scalarNode('node_with_a_looong_name')->end() ->enumNode('enum_with_default')->values(['this', 'that'])->defaultValue('this')->end() - ->enumNode('enum')->values(['this', 'that'])->end() + ->enumNode('enum')->values(['this', 'that', TestEnum::Ccc])->end() ->arrayNode('array') ->info('some info') ->canBeUnset() diff --git a/src/Symfony/Component/Config/Tests/Fixtures/TestEnum.php b/src/Symfony/Component/Config/Tests/Fixtures/TestEnum.php new file mode 100644 index 0000000000000..d019749f37ee0 --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Fixtures/TestEnum.php @@ -0,0 +1,10 @@ + Date: Wed, 18 Jan 2023 15:05:54 +0100 Subject: [PATCH 160/475] [DependencyInjection] Add missing template notation on ServiceLocator --- src/Symfony/Component/DependencyInjection/ServiceLocator.php | 3 +++ src/Symfony/Component/ErrorHandler/DebugClassLoader.php | 4 ++-- src/Symfony/Contracts/Service/ServiceProviderInterface.php | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/ServiceLocator.php b/src/Symfony/Component/DependencyInjection/ServiceLocator.php index 3560ce3ccdd0e..fbb94fd97cfd1 100644 --- a/src/Symfony/Component/DependencyInjection/ServiceLocator.php +++ b/src/Symfony/Component/DependencyInjection/ServiceLocator.php @@ -23,6 +23,9 @@ /** * @author Robin Chalas * @author Nicolas Grekas + * + * @template-covariant T of mixed + * @implements ServiceProviderInterface */ class ServiceLocator implements ServiceProviderInterface, \Countable { diff --git a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php index 561a7aa0bf3be..ad7e20694a75c 100644 --- a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php +++ b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php @@ -384,7 +384,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array // Detect annotations on the class if ($doc = $this->parsePhpDoc($refl)) { - $classIsTemplate = isset($doc['template']); + $classIsTemplate = isset($doc['template']) || isset($doc['template-covariant']); foreach (['final', 'deprecated', 'internal'] as $annotation) { if (null !== $description = $doc[$annotation][0] ?? null) { @@ -531,7 +531,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array // To read method annotations $doc = $this->parsePhpDoc($method); - if (($classIsTemplate || isset($doc['template'])) && $method->hasReturnType()) { + if (($classIsTemplate || isset($doc['template']) || isset($doc['template-covariant'])) && $method->hasReturnType()) { unset($doc['return']); } diff --git a/src/Symfony/Contracts/Service/ServiceProviderInterface.php b/src/Symfony/Contracts/Service/ServiceProviderInterface.php index a28fd82ea49a4..c05e4bfe7bc35 100644 --- a/src/Symfony/Contracts/Service/ServiceProviderInterface.php +++ b/src/Symfony/Contracts/Service/ServiceProviderInterface.php @@ -19,7 +19,7 @@ * @author Nicolas Grekas * @author Mateusz Sip * - * @template T of mixed + * @template-covariant T of mixed */ interface ServiceProviderInterface extends ContainerInterface { From 2f490ac31f83fe030f1606f0bfb3b13eb2a633d4 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 17 Jan 2023 11:00:01 +0100 Subject: [PATCH 161/475] [HttpFoundation] Improve return type of Header::all --- src/Symfony/Component/HttpFoundation/HeaderBag.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpFoundation/HeaderBag.php b/src/Symfony/Component/HttpFoundation/HeaderBag.php index 0883024b3b50b..eb96b9dd93458 100644 --- a/src/Symfony/Component/HttpFoundation/HeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/HeaderBag.php @@ -63,7 +63,7 @@ public function __toString(): string * * @param string|null $key The name of the headers to return or null to get them all * - * @return array>|array + * @return ($key is null ? array> : array) */ public function all(string $key = null): array { From c5407ce64e93206d62b3b2ca3d42ba8f6be46e06 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Wed, 18 Jan 2023 13:47:51 +0100 Subject: [PATCH 162/475] [SecurityBundle] Make firewalls event dispatcher traceable on debug mode --- .../Bundle/SecurityBundle/CHANGELOG.md | 1 + ...eFirewallsEventDispatcherTraceablePass.php | 70 ++++++++++++++++++ .../Bundle/SecurityBundle/SecurityBundle.php | 4 + ...ewallsEventDispatcherTraceablePassTest.php | 73 +++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePassTest.php diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index def296d23df5e..f71cea472f7de 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Modify "icon.svg" to improve accessibility for blind/low vision users * Make `Security::login()` return the authenticator response * Deprecate the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead + * Make firewalls event dispatcher traceable on debug mode 6.2 --- diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php new file mode 100644 index 0000000000000..ee231a9398839 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; + +/** + * @author Mathieu Lechat + */ +class MakeFirewallsEventDispatcherTraceablePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) { + return; + } + + if (!$container->getParameter('kernel.debug') || !$container->has('debug.stopwatch')) { + return; + } + + $dispatchersId = []; + + foreach ($container->getParameter('security.firewalls') as $firewallName) { + $dispatcherId = 'security.event_dispatcher.'.$firewallName; + + if (!$container->has($dispatcherId)) { + continue; + } + + $dispatchersId[$dispatcherId] = 'debug.'.$dispatcherId; + + $container->register($dispatchersId[$dispatcherId], TraceableEventDispatcher::class) + ->setDecoratedService($dispatcherId) + ->setArguments([ + new Reference($dispatchersId[$dispatcherId].'.inner'), + new Reference('debug.stopwatch'), + new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE), + new Reference('request_stack', ContainerInterface::NULL_ON_INVALID_REFERENCE), + ]); + } + + foreach (['kernel.event_subscriber', 'kernel.event_listener'] as $tagName) { + foreach ($container->findTaggedServiceIds($tagName) as $taggedServiceId => $tags) { + $taggedServiceDefinition = $container->findDefinition($taggedServiceId); + $taggedServiceDefinition->clearTag($tagName); + + foreach ($tags as $tag) { + if ($dispatcherId = $tag['dispatcher'] ?? null) { + $tag['dispatcher'] = $dispatchersId[$dispatcherId] ?? $dispatcherId; + } + $taggedServiceDefinition->addTag($tagName, $tag); + } + } + } + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 3d90b1690a589..317a6e11d1211 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -15,6 +15,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\CleanRememberMeVerifierPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\MakeFirewallsEventDispatcherTraceablePass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfFeaturesPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterEntryPointPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterGlobalSecurityEventListenersPass; @@ -92,5 +93,8 @@ public function build(ContainerBuilder $container) AuthenticationEvents::ALIASES, SecurityEvents::ALIASES ))); + + // must be registered before DecoratorServicePass + $container->addCompilerPass(new MakeFirewallsEventDispatcherTraceablePass(), PassConfig::TYPE_OPTIMIZE, 10); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePassTest.php new file mode 100644 index 0000000000000..e156a2f6f51d4 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePassTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Stopwatch\Stopwatch; + +class MakeFirewallsEventDispatcherTraceablePassTest extends TestCase +{ + private $container; + + protected function setUp(): void + { + $this->container = new ContainerBuilder(); + $this->container->register('request_stack', \stdClass::class); + $this->container->register('event_dispatcher', EventDispatcher::class); + $this->container->register('debug.stopwatch', Stopwatch::class); + + $this->container->registerExtension(new SecurityExtension()); + $this->container->loadFromExtension('security', [ + 'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true]], + ]); + + $this->container->addCompilerPass(new DecoratorServicePass(), PassConfig::TYPE_OPTIMIZE); + $this->container->getCompilerPassConfig()->setRemovingPasses([]); + $this->container->getCompilerPassConfig()->setAfterRemovingPasses([]); + + $securityBundle = new SecurityBundle(); + $securityBundle->build($this->container); + } + + public function testEventDispatcherIsDecoratedOnDebugMode() + { + $this->container->setParameter('kernel.debug', true); + + $this->container->compile(); + + $dispatcherDefinition = $this->container->findDefinition('security.event_dispatcher.main'); + + $this->assertSame(TraceableEventDispatcher::class, $dispatcherDefinition->getClass()); + $this->assertSame( + [['name' => 'security.event_dispatcher.main']], + $dispatcherDefinition->getTag('event_dispatcher.dispatcher') + ); + } + + public function testEventDispatcherIsNotDecoratedOnNonDebugMode() + { + $this->container->setParameter('kernel.debug', false); + + $this->container->compile(); + + $dispatcherDefinition = $this->container->findDefinition('security.event_dispatcher.main'); + + $this->assertSame(EventDispatcher::class, $dispatcherDefinition->getClass()); + } +} From 990841528a66243d03028ddc582cac00bf8e2a50 Mon Sep 17 00:00:00 2001 From: Alexandre parent Date: Tue, 26 Jul 2022 11:02:10 -0400 Subject: [PATCH 163/475] [DependencyInjection] Allow attribute autoconfiguration on static methods --- .../AttributeAutoconfigurationPass.php | 2 +- .../Tests/Compiler/IntegrationTest.php | 23 +++++++++++++++++++ .../Tests/Fixtures/StaticMethodTag.php | 22 ++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/StaticMethodTag.php diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php index 645214bd467f0..c57b78d3f58e7 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php @@ -120,7 +120,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if ($this->methodAttributeConfigurators || $this->parameterAttributeConfigurators) { foreach ($classReflector->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodReflector) { - if ($methodReflector->isStatic() || $methodReflector->isConstructor() || $methodReflector->isDestructor()) { + if ($methodReflector->isConstructor() || $methodReflector->isDestructor()) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index e7fcac4623f84..c9364395f71de 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -47,6 +47,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerWithDefaultIndexMethodAndWithDefaultPriorityMethod; use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerWithDefaultPriorityMethod; use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerWithoutIndex; +use Symfony\Component\DependencyInjection\Tests\Fixtures\StaticMethodTag; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedConsumerWithExclude; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2; @@ -1025,6 +1026,28 @@ static function (ChildDefinition $definition) { self::assertTrue($service->hasBeenConfigured); } + public function testAttributeAutoconfigurationOnStaticMethod() + { + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration( + CustomMethodAttribute::class, + static function (ChildDefinition $d, CustomMethodAttribute $a, \ReflectionMethod $_r) { + $d->addTag('custom_tag', ['attribute' => $a->someAttribute]); + } + ); + + $container->register('service', StaticMethodTag::class) + ->setPublic(true) + ->setAutoconfigured(true); + + $container->compile(); + + $definition = $container->getDefinition('service'); + self::assertEquals([['attribute' => 'static']], $definition->getTag('custom_tag')); + + $container->get('service'); + } + public function testTaggedIteratorAndLocatorWithExclude() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/StaticMethodTag.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/StaticMethodTag.php new file mode 100644 index 0000000000000..d5362d849cf38 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/StaticMethodTag.php @@ -0,0 +1,22 @@ + 10000 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomMethodAttribute; + +final class StaticMethodTag +{ + #[CustomMethodAttribute('static')] + public static function method(): void + { + } +} From 9ab243547f5c3b4540e162ef2d7e451547767289 Mon Sep 17 00:00:00 2001 From: tourze Date: Sun, 20 Nov 2022 00:26:40 +0800 Subject: [PATCH 164/475] [Cache] Compatible with aliyun redis instance Some cloud provider's redis instance is just compatible in common use command, but not some special command. Just like aliyun redis instance, doc: https://help.aliyun.com/document_detail/26342.html It based on redis protocol, but not really like the redis I know... I found that `$host->info('Memory')` will return false/null sometime, so and more safe check will be better for those special redis server. --- src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php index 01128eb150714..cf826f106570e 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php @@ -292,13 +292,13 @@ private function getRedisEvictionPolicy(): string foreach ($hosts as $host) { $info = $host->info('Memory'); - if ($info instanceof ErrorInterface) { + if (false === $info || null === $info || $info instanceof ErrorInterface) { continue; } $info = $info['Memory'] ?? $info; - return $this->redisEvictionPolicy = $info['maxmemory_policy']; + return $this->redisEvictionPolicy = $info['maxmemory_policy'] ?? ''; } return $this->redisEvictionPolicy = ''; From 6936845476d5c276f53caf5a16ed6134173c61b8 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Wed, 21 Dec 2022 22:37:25 +0100 Subject: [PATCH 165/475] [VarDumper] Display invisible characters --- src/Symfony/Component/VarDumper/CHANGELOG.md | 1 + .../Component/VarDumper/Dumper/CliDumper.php | 9 +++++++++ .../Component/VarDumper/Dumper/HtmlDumper.php | 8 +++++++- .../VarDumper/Tests/Dumper/CliDumperTest.php | 3 ++- .../VarDumper/Tests/Dumper/HtmlDumperTest.php | 3 ++- .../Component/VarDumper/Tests/Fixtures/dumb-var.php | 13 +++++++++++-- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/VarDumper/CHANGELOG.md b/src/Symfony/Component/VarDumper/CHANGELOG.md index 3b29f419dc32d..9329875010537 100644 --- a/src/Symfony/Component/VarDumper/CHANGELOG.md +++ b/src/Symfony/Component/VarDumper/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add caster for `WeakMap` * Add support of named arguments to `dd()` and `dump()` to display the argument name * Add support for `Relay\Relay` + * Add display of invisible characters 6.2 --- diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php index c52ac4dbfcbf8..7d6810e44050a 100644 --- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -51,6 +51,7 @@ class CliDumper extends AbstractDumper "\r" => '\r', "\033" => '\e', ]; + protected static $unicodeCharsRx = "/[\u{00A0}\u{00AD}\u{034F}\u{061C}\u{115F}\u{1160}\u{17B4}\u{17B5}\u{180E}\u{2000}-\u{200F}\u{202F}\u{205F}\u{2060}-\u{2064}\u{206A}-\u{206F}\u{3000}\u{2800}\u{3164}\u{FEFF}\u{FFA0}\u{1D159}\u{1D173}-\u{1D17A}]/u"; protected $collapseNextHash = false; protected $expandNextHash = false; @@ -450,6 +451,14 @@ protected function style(string $style, string $value, array $attr = []): string return $s.$endCchr; }, $value, -1, $cchrCount); + if (!($attr['binary'] ?? false)) { + $value = preg_replace_callback(static::$unicodeCharsRx, function ($c) use (&$cchrCount, $startCchr, $endCchr) { + ++$cchrCount; + + return $startCchr.'\u{'.strtoupper(dechex(mb_ord($c[0]))).'}'.$endCchr; + }, $value); + } + if ($this->colors) { if ($cchrCount && "\033" === $value[0]) { $value = substr($value, \strlen($startCchr)); diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index c818c919c43b1..0cc7baabb6c51 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -862,7 +862,6 @@ protected function style(string $style, string $value, array $attr = []): string } elseif ('private' === $style) { $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); } - $map = static::$controlCharsMap; if (isset($attr['ellipsis'])) { $class = 'sf-dump-ellipsis'; @@ -881,6 +880,7 @@ protected function style(string $style, string $value, array $attr = []): string } } + $map = static::$controlCharsMap; $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { $s = $b = '\u{'.strtoupper(dechex(mb_ord($c[0]))).'}'; + }, $v); + } + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { $attr['href'] = $href; } diff --git a/src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php b/src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php index b4780cfe92b9c..4993ee32415d9 100644 --- a/src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php @@ -51,7 +51,7 @@ public function testGet() $this->assertStringMatchesFormat( << 1 0 => &1 null "const" => 1.1 @@ -66,6 +66,7 @@ public function testGet() é\\x01test\\t\\n ing """ + "bo\\u{FEFF}m" => "te\\u{FEFF}st" "[]" => [] "res" => stream resource {@{$res} %A wrapper_type: "plainfile" diff --git a/src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php b/src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php index 1fd98640312e0..e48ed5e08b088 100644 --- a/src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php @@ -54,7 +54,7 @@ public function testGet() $this->assertStringMatchesFormat( <<array:24 [ +array:25 [ "number" => 1 0 => &1 null "const" => 1.1 @@ -69,6 +69,7 @@ public function testGet() é\\x01test\\t\\n ing """ + "bo "te[]" => [] "res" => stream resource @{$res} %A wrapper_type: "plainfile" diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php index fc48012f4d13f..3896f31026d67 100644 --- a/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php +++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\VarDumper\Tests\Fixture; if (!class_exists(\Symfony\Component\VarDumper\Tests\Fixture\DumbFoo::class)) { @@ -17,8 +26,8 @@ class DumbFoo $var = [ 'number' => 1, null, - 'const' => 1.1, true, false, NAN, INF, -INF, PHP_INT_MAX, - 'str' => "déjà\n", "\xE9\x01test\t\ning", + 'const' => 1.1, true, false, \NAN, \INF, -\INF, \PHP_INT_MAX, + 'str' => "déjà\n", "\xE9\x01test\t\ning", "bo\u{feff}m" => "te\u{feff}st", '[]' => [], 'res' => $g, 'obj' => $foo, From b377c121bf4b40c64ebeefb9b856a28b8c1910ed Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 21 Jan 2023 14:00:36 +0100 Subject: [PATCH 166/475] [Serializer] Replace the MissingConstructorArgumentsException class with MissingConstructorArgumentException --- .github/expected-missing-return-types.diff | 2 +- UPGRADE-6.3.md | 5 +++ src/Symfony/Component/Serializer/CHANGELOG.md | 1 + .../MissingConstructorArgumentException.php | 41 +++++++++++++++++++ .../MissingConstructorArgumentsException.php | 10 +++++ .../Normalizer/AbstractNormalizer.php | 8 ++-- .../Normalizer/AbstractObjectNormalizer.php | 6 +-- .../ConstructorArgumentsTestTrait.php | 13 ++++-- 8 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentException.php diff --git a/.github/expected-missing-return-types.diff b/.github/expected-missing-return-types.diff index c248018a4b9f6..22d5cf09bcf22 100644 --- a/.github/expected-missing-return-types.diff +++ b/.github/expected-missing-return-types.diff @@ -929,7 +929,7 @@ index 12c778cb80..4ad55fb3e1 100644 { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; @@ -311,5 +311,5 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn - * @throws MissingConstructorArgumentsException + * @throws MissingConstructorArgumentException */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index 89caa851669a4..9b9918aec2116 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -66,3 +66,8 @@ Validator --------- * Implementing the `ConstraintViolationInterface` without implementing the `getConstraint()` method is deprecated + +Serializer +---------- + + * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index d7799bb50236f..19eaa6a9cb8a4 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `XmlEncoder::SAVE_OPTIONS` context option + * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` 6.2 --- diff --git a/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentException.php b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentException.php new file mode 100644 index 0000000000000..3fdfaf605869e --- /dev/null +++ b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Exception; + +class MissingConstructorArgumentException extends MissingConstructorArgumentsException +{ + private string $class; + private string $missingArgument; + + /** + * @param class-string $class + */ + public function __construct(string $class, string $missingArgument, int $code = 0, \Throwable $previous = null) + { + $this->class = $class; + $this->missingArgument = $missingArgument; + + $message = sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $missingArgument); + + parent::__construct($message, $code, $previous, [$missingArgument]); + } + + public function getClass(): string + { + return $this->class; + } + + public function getMissingArgument(): string + { + return $this->missingArgument; + } +} diff --git a/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php index fe984a291f074..a5a71d00cf62a 100644 --- a/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php +++ b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Serializer\Exception; /** + * @deprecated since Symfony 6.3, use {@see MissingConstructorArgumentException} instead + * * @author Maxime VEBER */ class MissingConstructorArgumentsException extends RuntimeException @@ -23,16 +25,24 @@ class MissingConstructorArgumentsException extends RuntimeException public function __construct(string $message, int $code = 0, \Throwable $previous = null, array $missingArguments = []) { + if (!$this instanceof MissingConstructorArgumentException) { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s" class is deprecated, use "%s" instead.', __CLASS__, MissingConstructorArgumentException::class); + } + $this->missingArguments = $missingArguments; parent::__construct($message, $code, $previous); } /** + * @deprecated since Symfony 6.3, use {@see MissingConstructorArgumentException::getMissingArgument()} instead + * * @return string[] */ public function getMissingConstructorArguments(): array { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "%s::getMissingArgument()" instead.', __METHOD__, MissingConstructorArgumentException::class); + return $this->missingArguments; } } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 829e178407bd2..52e985815bf99 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -14,7 +14,7 @@ use Symfony\Component\Serializer\Exception\CircularReferenceException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\LogicException; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; @@ -308,7 +308,7 @@ protected function getConstructor(array &$data, string $class, array &$context, * @return object * * @throws RuntimeException - * @throws MissingConstructorArgumentsException + * @throws MissingConstructorArgumentException */ protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { @@ -381,7 +381,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params[] = null; } else { if (!isset($context['not_normalizable_value_exceptions'])) { - throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $constructorParameter->name), 0, null, [$constructorParameter->name]); + throw new MissingConstructorArgumentException($class, $constructorParameter->name); } $exception = NotNormalizableValueException::createForUnexpectedDataType( @@ -425,7 +425,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara } } catch (\ReflectionException $e) { throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e); - } catch (MissingConstructorArgumentsException $e) { + } catch (MissingConstructorArgumentException $e) { if (!$parameter->getType()->allowsNull()) { throw $e; } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 7c4c5fb41bd49..4a02c05a80fef 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -22,7 +22,7 @@ use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\LogicException; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; @@ -417,7 +417,7 @@ abstract protected function setAttributeValue(object $object, string $attribute, * * @throws NotNormalizableValueException * @throws ExtraAttributesException - * @throws MissingConstructorArgumentsException + * @throws MissingConstructorArgumentException * @throws LogicException */ private function validateAndDenormalize(array $types, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed @@ -565,7 +565,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri } $extraAttributesException ??= $e; - } catch (MissingConstructorArgumentsException $e) { + } catch (MissingConstructorArgumentException $e) { if (!$isUnionType) { throw $e; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php index 306c571f9c59d..9489136be2cb9 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer\Features; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Tests\Fixtures\NotSerializedConstructorArgumentDummy; @@ -63,8 +63,13 @@ public function testConstructorWithMissingData() $normalizer = $this->getDenormalizerForConstructArguments(); - $this->expectException(MissingConstructorArgumentsException::class); - $this->expectExceptionMessage('Cannot create an instance of "'.ConstructorArgumentsObject::class.'" from serialized data because its constructor requires parameter "bar" to be present.'); - $normalizer->denormalize($data, ConstructorArgumentsObject::class); + try { + $normalizer->denormalize($data, ConstructorArgumentsObject::class); + self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentException::class)); + } catch (MissingConstructorArgumentException $e) { + self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "bar" to be present.', ConstructorArgumentsObject::class), $e->getMessage()); + self::assertSame(ConstructorArgumentsObject::class, $e->getClass()); + self::assertSame('bar', $e->getMissingArgument()); + } } } From b2b0a751c736207c552d3bec53328800fa365d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Fri, 27 Jan 2023 08:53:39 +0100 Subject: [PATCH 167/475] Utilize shivammathur/setup-php to install Relay extension This is possible since https://github.com/shivammathur/setup-php/releases/tag/2.24.0 --- .github/workflows/integration-tests.yml | 10 +--------- .github/workflows/psalm.yml | 10 +--------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 618763da4544e..b9e050206afe6 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -134,19 +134,11 @@ jobs: uses: shivammathur/setup-php@v2 with: coverage: "none" - extensions: "json,couchbase-3.2.2,memcached,mongodb-1.12.0,redis,rdkafka,xsl,ldap,msgpack,igbinary" + extensions: "json,couchbase-3.2.2,memcached,mongodb-1.12.0,redis,rdkafka,xsl,ldap,relay-dev" ini-values: date.timezone=UTC,memory_limit=-1,default_socket_timeout=10,session.gc_probability=0,apc.enable_cli=1,zend.assertions=1 php-version: "${{ matrix.php }}" tools: pecl - - name: Install Relay - run: | - curl -L "https://builds.r2.relay.so/dev/relay-dev-php${{ matrix.php }}-debian-x86-64.tar.gz" | tar xz - cd relay-dev-php${{ matrix.php }}-debian-x86-64 - sudo cp relay.ini $(php-config --ini-dir) - sudo cp relay-pkg.so $(php-config --extension-dir)/relay.so - sudo sed -i "s/00000000-0000-0000-0000-000000000000/$(cat /proc/sys/kernel/random/uuid)/" $(php-config --extension-dir)/relay.so - - name: Display versions run: | php -r 'foreach (get_loaded_extensions() as $extension) echo $extension . " " . phpversion($extension) . PHP_EOL;' diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index e4d8ccfe4c1b8..77c1006a718a0 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -24,18 +24,10 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: '8.1' - extensions: "json,couchbase,memcached,mongodb,redis,xsl,ldap,dom" + extensions: "json,couchbase,memcached,mongodb,redis,xsl,ldap,dom,relay" ini-values: "memory_limit=-1" coverage: none - - name: Install Relay - run: | - curl -L "https://builds.r2.relay.so/dev/relay-dev-php8.1-debian-x86-64.tar.gz" | tar xz - cd relay-dev-php8.1-debian-x86-64 - sudo cp relay.ini $(php-config --ini-dir) - sudo cp relay-pkg.so $(php-config --extension-dir)/relay.so - sudo sed -i "s/00000000-0000-0000-0000-000000000000/$(cat /proc/sys/kernel/random/uuid)/" $(php-config --extension-dir)/relay.so - - name: Checkout target branch uses: actions/checkout@v3 with: From a4a1909cf4e9eef0c21092ef90f82bdda28c3b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Fri, 27 Jan 2023 18:49:45 +0100 Subject: [PATCH 168/475] CI: Use stable version of relay --- .github/workflows/integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index b9e050206afe6..623a74cd77248 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -134,7 +134,7 @@ jobs: uses: shivammathur/setup-php@v2 with: coverage: "none" - extensions: "json,couchbase-3.2.2,memcached,mongodb-1.12.0,redis,rdkafka,xsl,ldap,relay-dev" + extensions: "json,couchbase-3.2.2,memcached,mongodb-1.12.0,redis,rdkafka,xsl,ldap,relay" ini-values: date.timezone=UTC,memory_limit=-1,default_socket_timeout=10,session.gc_probability=0,apc.enable_cli=1,zend.assertions=1 php-version: "${{ matrix.php }}" tools: pecl From 9aded06c0cb5cf0b5c5cd98650a49f7cff55d60e Mon Sep 17 00:00:00 2001 From: Allison Guilhem Date: Tue, 24 Jan 2023 21:26:13 +0100 Subject: [PATCH 169/475] [HttpFoundation] inject SessionHandler in PdoSessionHandlerSchemaSubscriber --- .../PdoSessionHandlerSchemaSubscriber.php | 20 ++++++++++--------- .../PdoSessionHandlerSchemaSubscriberTest.php | 2 +- .../Storage/Handler/PdoSessionHandler.php | 7 +++++-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaSubscriber.php index a14a800cbb260..a2be4582bf421 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaSubscriber.php @@ -16,22 +16,24 @@ final class PdoSessionHandlerSchemaSubscriber extends AbstractSchemaSubscriber { - private iterable $pdoSessionHandlers; + private PdoSessionHandler $sessionHandler; - /** - * @param iterable $pdoSessionHandlers - */ - public function __construct(iterable $pdoSessionHandlers) + public function __construct(\SessionHandlerInterface $sessionHandler) { - $this->pdoSessionHandlers = $pdoSessionHandlers; + if ($sessionHandler instanceof PdoSessionHandler) { + $this->sessionHandler = $sessionHandler; + } + } + + public function getSubscribedEvents(): array + { + return isset($this->sessionHandler) ? parent::getSubscribedEvents() : []; } public function postGenerateSchema(GenerateSchemaEventArgs $event): void { $connection = $event->getEntityManager()->getConnection(); - foreach ($this->pdoSessionHandlers as $pdoSessionHandler) { - $pdoSessionHandler->configureSchema($event->getSchema(), $this->getIsSameDatabaseChecker($connection)); - } + $this->sessionHandler->configureSchema($event->getSchema(), $this->getIsSameDatabaseChecker($connection)); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php index 0e1f743803526..627848c0bcc0a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaSubscriberTest.php @@ -36,7 +36,7 @@ public function testPostGenerateSchemaPdo() ->method('configureSchema') ->with($schema, fn () => true); - $subscriber = new PdoSessionHandlerSchemaSubscriber([$pdoSessionHandler]); + $subscriber = new PdoSessionHandlerSchemaSubscriber($pdoSessionHandler); $subscriber->postGenerateSchema($event); } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 84b3e336fbdd2..d6e60a729e7ec 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -178,9 +178,12 @@ public function __construct(#[\SensitiveParameter] \PDO|string $pdoOrDsn = null, $this->ttl = $options['ttl'] ?? null; } - public function configureSchema(Schema $schema, \Closure $isSameDatabase): void + /** + * Adds the Table to the Schema if it doesn't exist. + */ + public function configureSchema(Schema $schema, \Closure $isSameDatabase = null): void { - if ($schema->hasTable($this->table) || !$isSameDatabase($this->getConnection()->exec(...))) { + if ($schema->hasTable($this->table) || ($isSameDatabase && !$isSameDatabase($this->getConnection()->exec(...)))) { return; } From 1a67728665332d39c1fc9cbbbde2da8b4cb90778 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 31 Jan 2023 18:55:36 +0100 Subject: [PATCH 170/475] [HttpFoundation] Fix defining expiry index in PdoSessionHandler::configureSchema() --- .../Session/Storage/Handler/PdoSessionHandler.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index d6e60a729e7ec..bc7b22850d8ca 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -225,6 +225,7 @@ public function configureSchema(Schema $schema, \Closure $isSameDatabase = null) throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); } $table->setPrimaryKey([$this->idCol]); + $table->addIndex([$this->lifetimeCol], $this->lifetimeCol.'_idx'); } /** @@ -259,7 +260,7 @@ public function createTable() try { $this->pdo->exec($sql); - $this->pdo->exec("CREATE INDEX expiry ON $this->table ($this->lifetimeCol)"); + $this->pdo->exec("CREATE INDEX {$this->lifetimeCol}_idx ON $this->table ($this->lifetimeCol)"); } catch (\PDOException $e) { $this->rollback(); From 4fcb8b50df91b6039be0a404162e6f8ff2300e86 Mon Sep 17 00:00:00 2001 From: Alexis Lefebvre Date: Tue, 31 Jan 2023 23:27:15 +0100 Subject: [PATCH 171/475] remove unused parameter transitionId in MermaidDumper --- src/Symfony/Component/Workflow/Dumper/MermaidDumper.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php b/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php index da11298ec9fe8..27b8dd0f4ea09 100644 --- a/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php +++ b/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php @@ -105,7 +105,6 @@ public function dump(Definition $definition, Marking $marking = null, array $opt $transitionOutput = $this->styleStatemachineTransition( $from, $to, - $transitionId, $transitionLabel, $transitionMeta ); @@ -211,7 +210,6 @@ private function validateTransitionType(string $transitionType): void private function styleStatemachineTransition( string $from, string $to, - int $transitionId, string $transitionLabel, array $transitionMeta ): array { From f3ab66e0b3e83da459003fe081ab3aca8c0a023c Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Sat, 14 Jan 2023 10:09:00 -0800 Subject: [PATCH 172/475] [Console] Add ReStructuredText descriptor --- src/Symfony/Component/Console/CHANGELOG.md | 1 + .../Descriptor/ReStructuredTextDescriptor.php | 272 ++++++++++++++++++ .../Console/Helper/DescriptorHelper.php | 2 + .../Console/Tests/Command/HelpCommandTest.php | 2 +- .../Console/Tests/Command/ListCommandTest.php | 2 +- .../ReStructuredTextDescriptorTest.php | 45 +++ .../Console/Tests/Fixtures/application_1.rst | 135 +++++++++ .../Console/Tests/Fixtures/application_2.rst | 216 ++++++++++++++ .../Tests/Fixtures/application_mbstring.rst | 178 ++++++++++++ .../Console/Tests/Fixtures/command_1.rst | 17 ++ .../Console/Tests/Fixtures/command_2.rst | 30 ++ .../Tests/Fixtures/command_mbstring.rst | 30 ++ .../Tests/Fixtures/input_argument_1.rst | 1 + .../Tests/Fixtures/input_argument_2.rst | 1 + .../Tests/Fixtures/input_argument_3.rst | 1 + .../Tests/Fixtures/input_argument_4.rst | 1 + .../input_argument_with_default_inf_value.rst | 1 + .../Fixtures/input_argument_with_style.rst | 1 + .../Tests/Fixtures/input_definition_1.rst | 0 .../Tests/Fixtures/input_definition_2.rst | 4 + .../Tests/Fixtures/input_definition_3.rst | 11 + .../Tests/Fixtures/input_definition_4.rst | 16 ++ .../Console/Tests/Fixtures/input_option_1.rst | 8 + .../Console/Tests/Fixtures/input_option_2.rst | 10 + .../Console/Tests/Fixtures/input_option_3.rst | 10 + .../Console/Tests/Fixtures/input_option_4.rst | 10 + .../Console/Tests/Fixtures/input_option_5.rst | 12 + .../Console/Tests/Fixtures/input_option_6.rst | 10 + .../input_option_with_default_inf_value.rst | 10 + .../Fixtures/input_option_with_style.rst | 10 + .../input_option_with_style_array.rst | 10 + .../Tests/Helper/DescriptorHelperTest.php | 1 + 32 files changed, 1056 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Console/Descriptor/ReStructuredTextDescriptor.php create mode 100644 src/Symfony/Component/Console/Tests/Descriptor/ReStructuredTextDescriptorTest.php create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/application_1.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/application_2.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/application_mbstring.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/command_1.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/command_2.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/command_mbstring.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_argument_1.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_argument_2.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_argument_3.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_argument_4.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_style.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_definition_1.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_definition_2.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_definition_3.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_definition_4.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_1.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_2.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_3.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_4.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_5.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_6.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_with_style.rst create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_with_style_array.rst diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index 0e010fae62ef0..ac55afccd0701 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Remove `exit` call in `Application` signal handlers. Commands will no longer be automatically interrupted after receiving signal other than `SIGUSR1` or `SIGUSR2` * Add `ProgressBar::setPlaceholderFormatter` to set a placeholder attached to a instance, instead of being global. + * Add `ReStructuredTextDescriptor` 6.2 --- diff --git a/src/Symfony/Component/Console/Descriptor/ReStructuredTextDescriptor.php b/src/Symfony/Component/Console/Descriptor/ReStructuredTextDescriptor.php new file mode 100644 index 0000000000000..d4423fd3483ea --- /dev/null +++ b/src/Symfony/Component/Console/Descriptor/ReStructuredTextDescriptor.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\String\UnicodeString; + +class ReStructuredTextDescriptor extends Descriptor +{ + //

    + private string $partChar = '='; + //

    + private string $chapterChar = '-'; + //

    + private string $sectionChar = '~'; + //

    + private string $subsectionChar = '.'; + //

    + private string $subsubsectionChar = '^'; + //
    + private string $paragraphsChar = '"'; + + private array $visibleNamespaces = []; + + public function describe(OutputInterface $output, object $object, array $options = []): void + { + $decorated = $output->isDecorated(); + $output->setDecorated(false); + + parent::describe($output, $object, $options); + + $output->setDecorated($decorated); + } + + /** + * Override parent method to set $decorated = true. + */ + protected function write(string $content, bool $decorated = true): void + { + parent::write($content, $decorated); + } + + protected function describeInputArgument(InputArgument $argument, array $options = []): void + { + $this->write( + $argument->getName() ?: ''."\n".str_repeat($this->paragraphsChar, Helper::width($argument->getName()))."\n\n" + .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') + .'- **Is required**: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'- **Is array**: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'- **Default**: ``'.str_replace("\n", '', var_export($argument->getDefault(), true)).'``' + ); + } + + protected function describeInputOption(InputOption $option, array $options = []): void + { + $name = '\-\-'.$option->getName(); + if ($option->isNegatable()) { + $name .= '|\-\-no-'.$option->getName(); + } + if ($option->getShortcut()) { + $name .= '|-'.str_replace('|', '|-', $option->getShortcut()); + } + + $optionDescription = $option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n\n", $option->getDescription())."\n\n" : ''; + $optionDescription = (new UnicodeString($optionDescription))->ascii(); + $this->write( + $name."\n".str_repeat($this->paragraphsChar, Helper::width($name))."\n\n" + .$optionDescription + .'- **Accept value**: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'- **Is value required**: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'- **Is multiple**: '.($option->isArray() ? 'yes' : 'no')."\n" + .'- **Is negatable**: '.($option->isNegatable() ? 'yes' : 'no')."\n" + .'- **Default**: ``'.str_replace("\n", '', var_export($option->getDefault(), true)).'``'."\n" + ); + } + + protected function describeInputDefinition(InputDefinition $definition, array $options = []): void + { + if ($showArguments = ((bool) $definition->getArguments())) { + $this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9))."\n\n"; + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + $this->describeInputArgument($argument); + } + } + + if ($nonDefaultOptions = $this->getNonDefaultOptions($definition)) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write("Options\n".str_repeat($this->subsubsectionChar, 7)."\n\n"); + foreach ($nonDefaultOptions as $option) { + $this->describeInputOption($option); + $this->write("\n"); + } + } + } + + protected function describeCommand(Command $command, array $options = []): void + { + if ($options['short'] ?? false) { + $this->write( + '``'.$command->getName()."``\n" + .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + ."Usage\n".str_repeat($this->paragraphsChar, 5)."\n\n" + .array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n") + ); + + return; + } + + $command->mergeApplicationDefinition(false); + + foreach ($command->getAliases() as $alias) { + $this->write('.. _'.$alias.":\n\n"); + } + $this->write( + $command->getName()."\n" + .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n" + .($command->getDescription() ? $command->getDescription()."\n\n" : '') + ."Usage\n".str_repeat($this->subsubsectionChar, 5)."\n\n" + .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n") + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + $definition = $command->getDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->write("\n\n"); + $this->describeInputDefinition($definition); + } + } + + protected function describeApplication(Application $application, array $options = []): void + { + $description = new ApplicationDescription($application, $options['namespace'] ?? null); + $title = $this->getApplicationTitle($application); + + $this->write($title."\n".str_repeat($this->partChar, Helper::width($title))); + $this->createTableOfContents($description, $application); + $this->describeCommands($application, $options); + } + + private function getApplicationTitle(Application $application): string + { + if ('UNKNOWN' === $application->getName()) { + return 'Console Tool'; + } + if ('UNKNOWN' !== $application->getVersion()) { + return sprintf('%s %s', $application->getName(), $application->getVersion()); + } + + return $application->getName(); + } + + private function describeCommands($application, array $options): void + { + $title = 'Commands'; + $this->write("\n\n$title\n".str_repeat($this->chapterChar, Helper::width($title))."\n\n"); + foreach ($this->visibleNamespaces as $namespace) { + if ('_global' === $namespace) { + $commands = $application->all(''); + $this->write('Global'."\n".str_repeat($this->sectionChar, Helper::width('Global'))."\n\n"); + } else { + $commands = $application->all($namespace); + $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n"); + } + + foreach ($this->removeAliasesAndHiddenCommands($commands) as $command) { + $this->describeCommand($command, $options); + $this->write("\n\n"); + } + } + } + + private function createTableOfContents(ApplicationDescription $description, Application $application): void + { + $this->setVisibleNamespaces($description); + $chapterTitle = 'Table of Contents'; + $this->write("\n\n$chapterTitle\n".str_repeat($this->chapterChar, Helper::width($chapterTitle))."\n\n"); + foreach ($this->visibleNamespaces as $namespace) { + if ('_global' === $namespace) { + $commands = $application->all(''); + } else { + $commands = $application->all($namespace); + $this->write("\n\n"); + $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n"); + } + $commands = $this->removeAliasesAndHiddenCommands($commands); + + $this->write("\n\n"); + $this->write(implode("\n", array_map(static fn ($commandName) => sprintf('- `%s`_', $commandName), array_keys($commands)))); + } + } + + private function getNonDefaultOptions(InputDefinition $definition): array + { + $globalOptions = [ + 'help', + 'quiet', + 'verbose', + 'version', + 'ansi', + 'no-interaction', + ]; + $nonDefaultOptions = []; + foreach ($definition->getOptions() as $option) { + // Skip global options. + if (!\in_array($option->getName(), $globalOptions)) { + $nonDefaultOptions[] = $option; + } + } + + return $nonDefaultOptions; + } + + private function setVisibleNamespaces(ApplicationDescription $description): void + { + $commands = $description->getCommands(); + foreach ($description->getNamespaces() as $namespace) { + try { + $namespaceCommands = $namespace['commands']; + foreach ($namespaceCommands as $key => $commandName) { + if (!\array_key_exists($commandName, $commands)) { + // If the array key does not exist, then this is an alias. + unset($namespaceCommands[$key]); + } elseif ($commands[$commandName]->isHidden()) { + unset($namespaceCommands[$key]); + } + } + if (!$namespaceCommands) { + // If the namespace contained only aliases or hidden commands, skip the namespace. + continue; + } + } catch (\Exception) { + } + $this->visibleNamespaces[] = $namespace['id']; + } + } + + private function removeAliasesAndHiddenCommands(array $commands): array + { + foreach ($commands as $key => $command) { + if ($command->isHidden() || \in_array($key, $command->getAliases(), true)) { + unset($commands[$key]); + } + } + unset($commands['completion']); + + return $commands; + } +} diff --git a/src/Symfony/Component/Console/Helper/DescriptorHelper.php b/src/Symfony/Component/Console/Helper/DescriptorHelper.php index 3015ff08d2c04..7189ff446e5d7 100644 --- a/src/Symfony/Component/Console/Helper/DescriptorHelper.php +++ b/src/Symfony/Component/Console/Helper/DescriptorHelper.php @@ -14,6 +14,7 @@ use Symfony\Component\Console\Descriptor\DescriptorInterface; use Symfony\Component\Console\Descriptor\JsonDescriptor; use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Exception\InvalidArgumentException; @@ -38,6 +39,7 @@ public function __construct() ->register('xml', new XmlDescriptor()) ->register('json', new JsonDescriptor()) ->register('md', new MarkdownDescriptor()) + ->register('rst', new ReStructuredTextDescriptor()) ; } diff --git a/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php b/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php index 0e8a7f4f7fd1a..4fed2303a9426 100644 --- a/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php @@ -87,7 +87,7 @@ public function provideCompletionSuggestions() { yield 'option --format' => [ ['--format', ''], - ['txt', 'xml', 'json', 'md'], + ['txt', 'xml', 'json', 'md', 'rst'], ]; yield 'nothing' => [ diff --git a/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php b/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php index 0f78cf7ee3202..bb7520feade07 100644 --- a/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php @@ -131,7 +131,7 @@ public function provideCompletionSuggestions() { yield 'option --format' => [ ['--format', ''], - ['txt', 'xml', 'json', 'md'], + ['txt', 'xml', 'json', 'md', 'rst'], ]; yield 'namespace' => [ diff --git a/src/Symfony/Component/Console/Tests/Descriptor/ReStructuredTextDescriptorTest.php b/src/Symfony/Component/Console/Tests/Descriptor/ReStructuredTextDescriptorTest.php new file mode 100644 index 0000000000000..6179b1760d808 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Descriptor/ReStructuredTextDescriptorTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor; +use Symfony\Component\Console\Tests\Fixtures\DescriptorApplicationMbString; +use Symfony\Component\Console\Tests\Fixtures\DescriptorCommandMbString; + +class ReStructuredTextDescriptorTest extends AbstractDescriptorTest +{ + public function getDescribeCommandTestData() + { + return $this->getDescriptionTestData(array_merge( + ObjectsProvider::getCommands(), + ['command_mbstring' => new DescriptorCommandMbString()] + )); + } + + public function getDescribeApplicationTestData() + { + return $this->getDescriptionTestData(array_merge( + ObjectsProvider::getApplications(), + ['application_mbstring' => new DescriptorApplicationMbString()] + )); + } + + protected function getDescriptor() + { + return new ReStructuredTextDescriptor(); + } + + protected function getFormat() + { + return 'rst'; + } +} diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_1.rst b/src/Symfony/Component/Console/Tests/Fixtures/application_1.rst new file mode 100644 index 0000000000000..5da38d0ff8dc0 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_1.rst @@ -0,0 +1,135 @@ +Console Tool +============ + +Table of Contents +----------------- + + + +- `help`_ +- `list`_ + +Commands +-------- + +Global +~~~~~~ + +help +.... + +Display help for a command + +Usage +^^^^^ + +- ``help [--format FORMAT] [--raw] [--] []`` + +The help command displays help for a given command: + + %%PHP_SELF%% help list + +You can also output the help in other formats by using the --format option: + + %%PHP_SELF%% help --format=xml list + +To display the list of available commands, please use the list command. + +Arguments +^^^^^^^^^ + +command_name + +Options +^^^^^^^ + +\-\-format +"""""""""" + +The output format (txt, xml, json, or md) + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``'txt'`` + +\-\-raw +""""""" + +To output raw command help + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` + + + +list +.... + +List commands + +Usage +^^^^^ + +- ``list [--raw] [--format FORMAT] [--short] [--] []`` + +The list command lists all commands: + + %%PHP_SELF%% list + +You can also display the commands for a specific namespace: + + %%PHP_SELF%% list test + +You can also output the information in other formats by using the --format option: + + %%PHP_SELF%% list --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + %%PHP_SELF%% list --raw + +Arguments +^^^^^^^^^ + +namespace + +Options +^^^^^^^ + +\-\-raw +""""""" + +To output raw command list + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` + +\-\-format +"""""""""" + +The output format (txt, xml, json, or md) + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``'txt'`` + +\-\-short +""""""""" + +To skip describing commands' arguments + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_2.rst b/src/Symfony/Component/Console/Tests/Fixtures/application_2.rst new file mode 100644 index 0000000000000..6426b62bd0428 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_2.rst @@ -0,0 +1,216 @@ +My Symfony application v1.0 +=========================== + +Table of Contents +----------------- + + + +- `help`_ +- `list`_ + +descriptor +~~~~~~~~~~ + + + +- `descriptor:command1`_ +- `descriptor:command2`_ +- `descriptor:command4`_ + +Commands +-------- + +Global +~~~~~~ + +help +.... + +Display help for a command + +Usage +^^^^^ + +- ``help [--format FORMAT] [--raw] [--] []`` + +The help command displays help for a given command: + + %%PHP_SELF%% help list + +You can also output the help in other formats by using the --format option: + + %%PHP_SELF%% help --format=xml list + +To display the list of available commands, please use the list command. + +Arguments +^^^^^^^^^ + +command_name + +Options +^^^^^^^ + +\-\-format +"""""""""" + +The output format (txt, xml, json, or md) + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``'txt'`` + +\-\-raw +""""""" + +To output raw command help + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` + + + +list +.... + +List commands + +Usage +^^^^^ + +- ``list [--raw] [--format FORMAT] [--short] [--] []`` + +The list command lists all commands: + + %%PHP_SELF%% list + +You can also display the commands for a specific namespace: + + %%PHP_SELF%% list test + +You can also output the information in other formats by using the --format option: + + %%PHP_SELF%% list --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + %%PHP_SELF%% list --raw + +Arguments +^^^^^^^^^ + +namespace + +Options +^^^^^^^ + +\-\-raw +""""""" + +To output raw command list + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` + +\-\-format +"""""""""" + +The output format (txt, xml, json, or md) + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``'txt'`` + +\-\-short +""""""""" + +To skip describing commands' arguments + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` + + + +descriptor +~~~~~~~~~~ + +.. _alias1: + +.. _alias2: + +descriptor:command1 +................... + +command 1 description + +Usage +^^^^^ + +- ``descriptor:command1`` +- ``alias1`` +- ``alias2`` + +command 1 help + + + +descriptor:command2 +................... + +command 2 description + +Usage +^^^^^ + +- ``descriptor:command2 [-o|--option_name] [--] `` +- ``descriptor:command2 -o|--option_name `` +- ``descriptor:command2 `` + +command 2 help + +Arguments +^^^^^^^^^ + +argument_name + +Options +^^^^^^^ + +\-\-option_name|-o +"""""""""""""""""" + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` + + + +.. _descriptor:alias_command4: + +.. _command4:descriptor: + +descriptor:command4 +................... + +Usage +^^^^^ + +- ``descriptor:command4`` +- ``descriptor:alias_command4`` +- ``command4:descriptor`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_mbstring.rst b/src/Symfony/Component/Console/Tests/Fixtures/application_mbstring.rst new file mode 100644 index 0000000000000..3ea1ebfd50f23 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_mbstring.rst @@ -0,0 +1,178 @@ +MbString åpplicätion +==================== + +Table of Contents +----------------- + + + +- `help`_ +- `list`_ + +descriptor +~~~~~~~~~~ + + + +- `descriptor:åèä`_ + +Commands +-------- + +Global +~~~~~~ + +help +.... + +Display help for a command + +Usage +^^^^^ + +- ``help [--format FORMAT] [--raw] [--] []`` + +The help command displays help for a given command: + + %%PHP_SELF%% help list + +You can also output the help in other formats by using the --format option: + + %%PHP_SELF%% help --format=xml list + +To display the list of available commands, please use the list command. + +Arguments +^^^^^^^^^ + +command_name + +Options +^^^^^^^ + +\-\-format +"""""""""" + +The output format (txt, xml, json, or md) + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``'txt'`` + +\-\-raw +""""""" + +To output raw command help + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` + + + +list +.... + +List commands + +Usage +^^^^^ + +- ``list [--raw] [--format FORMAT] [--short] [--] []`` + +The list command lists all commands: + + %%PHP_SELF%% list + +You can also display the commands for a specific namespace: + + %%PHP_SELF%% list test + +You can also output the information in other formats by using the --format option: + + %%PHP_SELF%% list --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + %%PHP_SELF%% list --raw + +Arguments +^^^^^^^^^ + +namespace + +Options +^^^^^^^ + +\-\-raw +""""""" + +To output raw command list + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` + +\-\-format +"""""""""" + +The output format (txt, xml, json, or md) + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``'txt'`` + +\-\-short +""""""""" + +To skip describing commands' arguments + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` + + + +descriptor +~~~~~~~~~~ + +descriptor:åèä +.............. + +command åèä description + +Usage +^^^^^ + +- ``descriptor:åèä [-o|--option_åèä] [--] `` +- ``descriptor:åèä -o|--option_name `` +- ``descriptor:åèä `` + +command åèä help + +Arguments +^^^^^^^^^ + +argument_åèä + +Options +^^^^^^^ + +\-\-option_åèä|-o +""""""""""""""""" + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/command_1.rst b/src/Symfony/Component/Console/Tests/Fixtures/command_1.rst new file mode 100644 index 0000000000000..a4d93a3dc22ce --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/command_1.rst @@ -0,0 +1,17 @@ +.. _alias1: + +.. _alias2: + +descriptor:command1 +................... + +command 1 description + +Usage +^^^^^ + +- ``descriptor:command1`` +- ``alias1`` +- ``alias2`` + +command 1 help diff --git a/src/Symfony/Component/Console/Tests/Fixtures/command_2.rst b/src/Symfony/Component/Console/Tests/Fixtures/command_2.rst new file mode 100644 index 0000000000000..3744aad788e9e --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/command_2.rst @@ -0,0 +1,30 @@ +descriptor:command2 +................... + +command 2 description + +Usage +^^^^^ + +- ``descriptor:command2 [-o|--option_name] [--] `` +- ``descriptor:command2 -o|--option_name `` +- ``descriptor:command2 `` + +command 2 help + +Arguments +^^^^^^^^^ + +argument_name + +Options +^^^^^^^ + +\-\-option_name|-o +"""""""""""""""""" + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/command_mbstring.rst b/src/Symfony/Component/Console/Tests/Fixtures/command_mbstring.rst new file mode 100644 index 0000000000000..d61163c2dda7c --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/command_mbstring.rst @@ -0,0 +1,30 @@ +descriptor:åèä +.............. + +command åèä description + +Usage +^^^^^ + +- ``descriptor:åèä [-o|--option_åèä] [--] `` +- ``descriptor:åèä -o|--option_name `` +- ``descriptor:åèä `` + +command åèä help + +Arguments +^^^^^^^^^ + +argument_åèä + +Options +^^^^^^^ + +\-\-option_åèä|-o +""""""""""""""""" + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_1.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_1.rst new file mode 100644 index 0000000000000..4db1cd215ae8a --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_1.rst @@ -0,0 +1 @@ +argument_name diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_2.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_2.rst new file mode 100644 index 0000000000000..4db1cd215ae8a --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_2.rst @@ -0,0 +1 @@ +argume 10000 nt_name diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_3.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_3.rst new file mode 100644 index 0000000000000..4db1cd215ae8a --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_3.rst @@ -0,0 +1 @@ +argument_name diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_4.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_4.rst new file mode 100644 index 0000000000000..4db1cd215ae8a --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_4.rst @@ -0,0 +1 @@ +argument_name diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.rst new file mode 100644 index 0000000000000..4db1cd215ae8a --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.rst @@ -0,0 +1 @@ +argument_name diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_style.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_style.rst new file mode 100644 index 0000000000000..4db1cd215ae8a --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_style.rst @@ -0,0 +1 @@ +argument_name diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_definition_1.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_definition_1.rst new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_definition_2.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_definition_2.rst new file mode 100644 index 0000000000000..0f5b76018be27 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_definition_2.rst @@ -0,0 +1,4 @@ +Arguments +^^^^^^^^^ + +argument_name diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_definition_3.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_definition_3.rst new file mode 100644 index 0000000000000..1a5e9b1ad04d5 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_definition_3.rst @@ -0,0 +1,11 @@ +Options +^^^^^^^ + +\-\-option_name|-o +"""""""""""""""""" + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_definition_4.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_definition_4.rst new file mode 100644 index 0000000000000..1c65681ab0b4e --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_definition_4.rst @@ -0,0 +1,16 @@ +Arguments +^^^^^^^^^ + +argument_name + +Options +^^^^^^^ + +\-\-option_name|-o +"""""""""""""""""" + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_1.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_option_1.rst new file mode 100644 index 0000000000000..93662791f33a1 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_1.rst @@ -0,0 +1,8 @@ +\-\-option_name|-o +"""""""""""""""""" + +- **Accept value**: no +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``false`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_2.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_option_2.rst new file mode 100644 index 0000000000000..0a8a14c66638a --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_2.rst @@ -0,0 +1,10 @@ +\-\-option_name|-o +"""""""""""""""""" + +option description + +- **Accept value**: yes +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``'default_value'`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_3.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_option_3.rst new file mode 100644 index 0000000000000..45374910c15f8 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_3.rst @@ -0,0 +1,10 @@ +\-\-option_name|-o +"""""""""""""""""" + +option description + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``NULL`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_4.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_option_4.rst new file mode 100644 index 0000000000000..fe81fc1fe9355 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_4.rst @@ -0,0 +1,10 @@ +\-\-option_name|-o +"""""""""""""""""" + +option description + +- **Accept value**: yes +- **Is value required**: no +- **Is multiple**: yes +- **Is negatable**: no +- **Default**: ``array ()`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_5.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_option_5.rst new file mode 100644 index 0000000000000..c2b6a4cd70948 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_5.rst @@ -0,0 +1,12 @@ +\-\-option_name|-o +"""""""""""""""""" + +multiline + +option description + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``NULL`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.rst new file mode 100644 index 0000000000000..748cad7202252 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.rst @@ -0,0 +1,10 @@ +\-\-option_name|-o|-O +""""""""""""""""""""" + +option with multiple shortcuts + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``NULL`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.rst new file mode 100644 index 0000000000000..8a99db1babeb4 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.rst @@ -0,0 +1,10 @@ +\-\-option_name|-o +"""""""""""""""""" + +option description + +- **Accept value**: yes +- **Is value required**: no +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``INF`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_style.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_style.rst new file mode 100644 index 0000000000000..47a9886eedf2b --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_style.rst @@ -0,0 +1,10 @@ +\-\-option_name|-o +"""""""""""""""""" + +option description + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: no +- **Is negatable**: no +- **Default**: ``'style'`` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_style_array.rst b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_style_array.rst new file mode 100644 index 0000000000000..29a822e56331d --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_style_array.rst @@ -0,0 +1,10 @@ +\-\-option_name|-o +"""""""""""""""""" + +option description + +- **Accept value**: yes +- **Is value required**: yes +- **Is multiple**: yes +- **Is negatable**: no +- **Default**: ``array ( 0 => 'Hello', 1 => 'world',)`` diff --git a/src/Symfony/Component/Console/Tests/Helper/DescriptorHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/DescriptorHelperTest.php index 97bae77c51fcc..57435a3e7f1d1 100644 --- a/src/Symfony/Component/Console/Tests/Helper/DescriptorHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/DescriptorHelperTest.php @@ -24,6 +24,7 @@ public function testGetFormats() 'xml', 'json', 'md', + 'rst', ]; $this->assertSame($expectedFormats, $helper->getFormats()); } From bd695366bb3d9ee69080089100a06f2153e4f68e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 2 Feb 2023 08:48:58 +0100 Subject: [PATCH 173/475] Fix LICENSE year --- src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/PagerDuty/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/Termii/LICENSE | 2 +- src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE b/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE index 074eb2b39259e..733c826ebcd63 100644 --- a/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Bandwidth/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022-2023 Fabien Potencier +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE b/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE index 074eb2b39259e..733c826ebcd63 100644 --- a/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Isendpro/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022-2023 Fabien Potencier +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE b/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE index 074eb2b39259e..733c826ebcd63 100644 --- a/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/LineNotify/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022-2023 Fabien Potencier +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE b/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE index 074eb2b39259e..733c826ebcd63 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Mastodon/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022-2023 Fabien Potencier +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/PagerDuty/LICENSE b/src/Symfony/Component/Notifier/Bridge/PagerDuty/LICENSE index f961401699b27..3ed9f412ce53d 100644 --- a/src/Symfony/Component/Notifier/Bridge/PagerDuty/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/PagerDuty/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2023 Fabien Potencier +Copyright (c) 2023-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE b/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE index 074eb2b39259e..733c826ebcd63 100644 --- a/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Plivo/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022-2023 Fabien Potencier +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE b/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE index 074eb2b39259e..733c826ebcd63 100644 --- a/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/RingCentral/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022-2023 Fabien Potencier +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE b/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE index 074eb2b39259e..733c826ebcd63 100644 --- a/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Termii/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022-2023 Fabien Potencier +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE b/src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE index 074eb2b39259e..733c826ebcd63 100644 --- a/src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE +++ b/src/Symfony/Component/Notifier/Bridge/Twitter/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022-2023 Fabien Potencier +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c021ce79e8d32b14a29d7e69b41ff517e41497b6 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 2 Feb 2023 09:11:04 +0100 Subject: [PATCH 174/475] [Security] Return 403 instead of 500 when no firewall is defined --- .../EventListener/ErrorListener.php | 30 +++++++++++-------- .../Tests/EventListener/ErrorListenerTest.php | 11 ++++++- .../Core/Exception/AccessDeniedException.php | 3 ++ .../Exception/AuthenticationException.php | 2 ++ .../Exception/NotAnEntryPointException.php | 3 ++ 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php index 2e8b75afcf585..dadd32dda867d 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php @@ -71,15 +71,17 @@ public function logKernelException(ExceptionEvent $event) // There's no specific status code defined in the configuration for this exception if (!$throwable instanceof HttpExceptionInterface) { $class = new \ReflectionClass($throwable); - $attributes = $class->getAttributes(WithHttpStatus::class, \ReflectionAttribute::IS_INSTANCEOF); - if ($attributes) { - /** @var WithHttpStatus $instance */ - $instance = $attributes[0]->newInstance(); + do { + if ($attributes = $class->getAttributes(WithHttpStatus::class, \ReflectionAttribute::IS_INSTANCEOF)) { + /** @var WithHttpStatus $instance */ + $instance = $attributes[0]->newInstance(); - $throwable = new HttpException($instance->statusCode, $throwable->getMessage(), $throwable, $instance->headers); - $event->setThrowable($throwable); - } + $throwable = new HttpException($instance->statusCode, $throwable->getMessage(), $throwable, $instance->headers); + $event->setThrowable($throwable); + break; + } + } while ($class = $class->getParentClass()); } $e = FlattenException::createFromThrowable($throwable); @@ -185,14 +187,16 @@ private function resolveLogLevel(\Throwable $throwable): string } } - $attributes = (new \ReflectionClass($throwable))->getAttributes(WithLogLevel::class); + $class = new \ReflectionClass($throwable); - if ($attributes) { - /** @var WithLogLevel $instance */ - $instance = $attributes[0]->newInstance(); + do { + if ($attributes = $class->getAttributes(WithLogLevel::class)) { + /** @var WithLogLevel $instance */ + $instance = $attributes[0]->newInstance(); - return $instance->level; - } + return $instance->level; + } + } while ($class = $class->getParentClass()); if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() >= 500) { return LogLevel::CRITICAL; diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php index d99f0b439174b..7f6d0bea2abf5 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php @@ -123,7 +123,7 @@ public function testHandleWithLoggerAndCustomConfiguration() public function testHandleWithLogLevelAttribute() { $request = new Request(); - $event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new WarningWithLogLevelAttribute()); + $event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new ChildOfWarningWithLogLevelAttribute()); $logger = new TestLogger(); $l = new ErrorListener('not used', $logger); @@ -280,6 +280,7 @@ public static function exceptionWithAttributeProvider() { yield [new WithCustomUserProvidedAttribute(), 208, ['name' => 'value']]; yield [new WithGeneralAttribute(), 412, ['some' => 'thing']]; + yield [new ChildOfWithGeneralAttribute(), 412, ['some' => 'thing']]; } } @@ -341,7 +342,15 @@ class WithGeneralAttribute extends \Exception { } +class ChildOfWithGeneralAttribute extends WithGeneralAttribute +{ +} + #[WithLogLevel(LogLevel::WARNING)] class WarningWithLogLevelAttribute extends \Exception { } + +class ChildOfWarningWithLogLevelAttribute extends WarningWithLogLevelAttribute +{ +} diff --git a/src/Symfony/Component/Security/Core/Exception/AccessDeniedException.php b/src/Symfony/Component/Security/Core/Exception/AccessDeniedException.php index 79fb6eb668960..126149049bda1 100644 --- a/src/Symfony/Component/Security/Core/Exception/AccessDeniedException.php +++ b/src/Symfony/Component/Security/Core/Exception/AccessDeniedException.php @@ -11,11 +11,14 @@ namespace Symfony\Component\Security\Core\Exception; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; + /** * AccessDeniedException is thrown when the account has not the required role. * * @author Fabien Potencier */ +#[WithHttpStatus(403)] class AccessDeniedException extends RuntimeException { private array $attributes = []; diff --git a/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php b/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php index 298bc78cd9b3f..69ac3d6ec4a4d 100644 --- a/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php +++ b/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Core\Exception; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; /** @@ -19,6 +20,7 @@ * @author Fabien Potencier * @author Alexander */ +#[WithHttpStatus(401)] class AuthenticationException extends RuntimeException { /** @internal */ diff --git a/src/Symfony/Component/Security/Http/EntryPoint/Exception/NotAnEntryPointException.php b/src/Symfony/Component/Security/Http/EntryPoint/Exception/NotAnEntryPointException.php index e421dcf0cd67b..80a6fb6e8f456 100644 --- a/src/Symfony/Component/Security/Http/EntryPoint/Exception/NotAnEntryPointException.php +++ b/src/Symfony/Component/Security/Http/EntryPoint/Exception/NotAnEntryPointException.php @@ -11,12 +11,15 @@ namespace Symfony\Component\Security\Http\EntryPoint\Exception; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; + /** * Thrown by generic decorators when a decorated authenticator does not implement * {@see AuthenticationEntryPointInterface}. * * @author Robin Chalas */ +#[WithHttpStatus(401)] class NotAnEntryPointException extends \RuntimeException { } From 0127ef47036096ca3e148fdf75a0100aaa5411b5 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 2 Feb 2023 17:49:29 +0100 Subject: [PATCH 175/475] [WebProfilerBundle] Remove obsolete elements and attributes --- .../Resources/views/Collector/form.html.twig | 10 +++++----- .../Resources/views/Collector/logger.html.twig | 4 ++-- .../Resources/views/Collector/notifier.html.twig | 2 +- .../Resources/views/Collector/time.html.twig | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig index 7f389405141ab..fb5b8b7dadaff 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -587,7 +587,7 @@ - + @@ -630,7 +630,7 @@
    PropertyProperty Value
    - + @@ -673,7 +673,7 @@
    PropertyProperty Value
    - + @@ -708,7 +708,7 @@
    OptionOption Passed Value Resolved Value
    - + @@ -727,7 +727,7 @@
    OptionOption Value
    - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index d826993ee7ce8..19c187f069d4c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -41,7 +41,7 @@ transform: translateY(1px); } .log-filters .log-filter summary .icon { - height: 18px;mai + height: 18px; width: 18px; margin: 0 7px 0 0; } @@ -336,7 +336,7 @@
    VariableVariable Value
    - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig index b0c9219b02b79..f72f56b69ea64 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig @@ -29,7 +29,7 @@ {% block head %} {{ parent() }} - '; + $importMapRenderer->expects($this->once()) + ->method('render') + ->with('application') + ->willReturn($expected); + $runtime = new ImportMapRuntime($importMapRenderer); + + $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); + $mockRuntimeLoader + ->method('load') + ->willReturnMap([ + [ImportMapRuntime::class, $runtime], + ]) + ; + $twig->addRuntimeLoader($mockRuntimeLoader); + + $this->assertSame($expected, $twig->render('template')); + } +} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 8cf462fea5938..cac49224c88be 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -26,6 +26,7 @@ "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", "symfony/form": "^6.3", diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index 2a2c1983166b0..6d39e9440f02a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -14,6 +14,7 @@ use Psr\Container\ContainerInterface; use Psr\Link\EvolvableLinkInterface; use Psr\Link\LinkInterface; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; use Symfony\Component\Form\Extension\Core\Type\FormType; @@ -44,6 +45,7 @@ use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; use Symfony\Component\WebLink\GenericLinkProvider; use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Component\WebLink\Link; use Symfony\Contracts\Service\Attribute\Required; use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; @@ -95,6 +97,7 @@ public static function getSubscribedServices(): array 'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class, 'parameter_bag' => '?'.ContainerBagInterface::class, 'web_link.http_header_serializer' => '?'.HttpHeaderSerializer::class, + 'asset_mapper.importmap.manager' => '?'.ImportMapManager::class, ]; } @@ -409,7 +412,7 @@ protected function addLink(Request $request, LinkInterface $link): void /** * @param LinkInterface[] $links */ - protected function sendEarlyHints(iterable $links, Response $response = null): Response + protected function sendEarlyHints(iterable $links = [], Response $response = null, bool $preloadJavaScriptModules = false): Response { 10000 if (!$this->container->has('web_link.http_header_serializer')) { throw new \LogicException('You cannot use the "sendEarlyHints" method if the WebLink component is not available. Try running "composer require symfony/web-link".'); @@ -418,6 +421,17 @@ protected function sendEarlyHints(iterable $links, Response $response = null): R $response ??= new Response(); $populatedLinks = []; + + if ($preloadJavaScriptModules) { + if (!$this->container->has('asset_mapper.importmap.manager')) { + throw new \LogicException('You cannot use the JavaScript modules method if the AssetMapper component is not available. Try running "composer require symfony/asset-mapper".'); + } + + foreach ($this->container->get('asset_mapper.importmap.manager')->getModulesToPreload() as $url) { + $populatedLinks[] = new Link('modulepreload', $url); + } + } + foreach ($links as $link) { if ($link instanceof EvolvableLinkInterface && !$link->getRels()) { $link = $link->withRel('preload'); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 9da5b91bb3bb0..a76b84ad8337c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -24,6 +24,7 @@ class UnusedTagsPass implements CompilerPassInterface private const KNOWN_TAGS = [ 'annotations.cached_reader', 'assets.package', + 'asset_mapper.compiler', 'auto_alias', 'cache.pool', 'cache.pool.clearer', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index c5378238b92a4..d668d435a42e2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -16,6 +16,8 @@ use Psr\Log\LogLevel; use Symfony\Bundle\FullStack; use Symfony\Component\Asset\Package; +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeBuilder; @@ -161,6 +163,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addSessionSection($rootNode); $this->addRequestSection($rootNode); $this->addAssetsSection($rootNode, $enableIfStandalone); + $this->addAssetMapperSection($rootNode, $enableIfStandalone); $this->addTranslatorSection($rootNode, $enableIfStandalone); $this->addValidationSection($rootNode, $enableIfStandalone); $this->addAnnotationsSection($rootNode, $willBeAvailable); @@ -810,6 +813,97 @@ private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enabl ; } + private function addAssetMapperSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('asset_mapper') + ->info('Asset Mapper configuration') + ->{$enableIfStandalone('symfony/asset-mapper', AssetMapper::class)}() + ->fixXmlConfig('path') + ->fixXmlConfig('extension') + ->fixXmlConfig('importmap_script_attribute') + ->children() + // add array node called "paths" that will be an array of strings + ->arrayNode('paths') + ->info('Directories that hold assets that should be in the mapper. Can be a simple array of an array of ["path/to/assets": "namespace"]') + ->example(['assets/']) + ->normalizeKeys(false) + ->useAttributeAsKey('namespace') + ->beforeNormalization() + ->always() + ->then(function ($v) { + $result = []; + foreach ($v as $key => $item) { + // "dir" => "namespace" + if (\is_string($key)) { + $result[$key] = $item; + + continue; + } + + if (\is_array($item)) { + // $item = ["namespace" => "the/namespace", "value" => "the/dir"] + $result[$item['value']] = $item['namespace'] ?? ''; + } else { + // $item = "the/dir" + $result[$item] = ''; + } + } + + return $result; + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->booleanNode('server') + ->info('If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default)') + ->defaultValue($this->debug) + ->end() + ->scalarNode('public_prefix') + ->info('The public path where the assets will be written to (and served from when "server" is true)') + ->defaultValue('/assets/') + ->end() + ->booleanNode('strict_mode') + ->info('If true, an exception will be thrown if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import \'./non-existent.js\'"') + ->defaultValue(true) + ->end() + ->arrayNode('extensions') + ->info('Key-value pair of file extensions set to their mime type.') + ->normalizeKeys(false) + ->useAttributeAsKey('extension') + ->example(['.zip' => 'application/zip']) + ->prototype('scalar')->end() + ->end() + ->scalarNode('importmap_path') + ->info('The path of the importmap.php file.') + ->defaultValue('%kernel.project_dir%/importmap.php') + ->end() + ->scalarNode('importmap_polyfill') + ->info('URL of the ES Module Polyfill to use, false to disable. Defaults to using a CDN URL.') + ->defaultValue(null) + ->end() + ->arrayNode('importmap_script_attributes') + ->info('Key-value pair of attributes to add to script tags output for the importmap.') + ->normalizeKeys(false) + ->useAttributeAsKey('key') + ->example(['data-turbo-track' => 'reload']) + ->prototype('scalar')->end() + ->end() + ->scalarNode('vendor_dir') + ->info('The directory to store JavaScript vendors.') + ->defaultValue('%kernel.project_dir%/assets/vendor') + ->end() + ->scalarNode('provider') + ->info('The provider (CDN) to use', class_exists(ImportMapManager::class) ? sprintf(' (e.g.: "%s").', implode('", "', ImportMapManager::PROVIDERS)) : '.') + ->defaultValue('jspm') + ->end() + ->end() + ->end() + ->end() + ; + } + private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 36622ee23d604..7acee5c278e07 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -31,6 +31,8 @@ use Symfony\Bundle\FullStack; use Symfony\Bundle\MercureBundle\MercureBundle; use Symfony\Component\Asset\PackageInterface; +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; use Symfony\Component\BrowserKit\AbstractBrowser; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -45,6 +47,7 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\ResourceCheckerInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; @@ -330,6 +333,14 @@ public function load(array $configs, ContainerBuilder $container) $this->registerAssetsConfiguration($config['assets'], $container, $loader); } + if ($this->readConfigEnabled('asset_mapper', $container, $config['asset_mapper'])) { + if (!class_exists(AssetMapper::class)) { + throw new LogicException('AssetMapper support cannot be enabled as the AssetMapper component is not installed. Try running "composer require symfony/asset-mapper".'); + } + + $this->registerAssetMapperConfiguration($config['asset_mapper'], $container, $loader, $this->readConfigEnabled('assets', $container, $config['assets'])); + } + if ($this->readConfigEnabled('http_client', $container, $config['http_client'])) { $this->registerHttpClientConfiguration($config['http_client'], $container, $loader); } @@ -1231,6 +1242,57 @@ private function registerAssetsConfiguration(array $config, ContainerBuilder $co } } + private function registerAssetMapperConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $assetEnabled): void + { + $loader->load('asset_mapper.php'); + + if (!$assetEnabled) { + $container->removeDefinition('asset_mapper.asset_package'); + } + + $publicDirName = $this->getPublicDirectoryName($container); + $container->getDefinition('asset_mapper') + ->setArgument(3, $config['public_prefix']) + ->setArgument(4, $publicDirName) + ->setArgument(5, $config['extensions']) + ; + + $paths = $config['paths']; + foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) { + if ($container->fileExists($dir = $bundle['path'].'/Resources/public') || $container->fileExists($dir = $bundle['path'].'/public')) { + $paths[$dir] = sprintf('bundles/%s', preg_replace('/bundle$/', '', strtolower($name))); + } + } + $container->getDefinition('asset_mapper.repository') + ->setArgument(0, $paths); + + $container->getDefinition('asset_mapper.command.compile') + ->setArgument(4, $publicDirName); + + if (!$config['server']) { + $container->removeDefinition('asset_mapper.dev_server_subscriber'); + } + + $container->getDefinition('asset_mapper.compiler.css_asset_url_compiler') + ->setArgument(0, $config['strict_mode']); + + $container->getDefinition('asset_mapper.compiler.javascript_import_path_compiler') + ->setArgument(0, $config['strict_mode']); + + $container + ->getDefinition('asset_mapper.importmap.manager') + ->replaceArgument(1, $config['importmap_path']) + ->replaceArgument(2, $config['vendor_dir']) + ->replaceArgument(3, $config['provider']) + ; + + $container + ->getDefinition('asset_mapper.importmap.renderer') + ->replaceArgument(2, $config['importmap_polyfill'] ?? ImportMapManager::POLYFILL_URL) + ->replaceArgument(3, $config['importmap_script_attributes']) + ; + } + /** * Returns a definition for an asset package. */ @@ -2994,4 +3056,20 @@ private function writeConfigEnabled(string $path, bool $value, array &$config): $this->configsEnabled[$path] = $value; $config['enabled'] = $value; } + + private function getPublicDirectoryName(ContainerBuilder $container): string + { + $defaultPublicDir = 'public'; + + $composerFilePath = $container->getParameter('kernel.project_dir').'/composer.json'; + + if (!file_exists($composerFilePath)) { + return $defaultPublicDir; + } + + $container->addResource(new FileResource($composerFilePath)); + $composerConfig = json_decode(file_get_contents($composerFilePath), true); + + return $composerConfig['extra']['public-dir'] ?? $defaultPublicDir; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php new file mode 100644 index 0000000000000..5d471ea623258 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperCompiler; +use Symfony\Component\AssetMapper\AssetMapperDevServerSubscriber; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\AssetMapperRepository; +use Symfony\Component\AssetMapper\Command\AssetMapperCompileCommand; +use Symfony\Component\AssetMapper\Command\ImportMapExportCommand; +use Symfony\Component\AssetMapper\Command\ImportMapRemoveCommand; +use Symfony\Component\AssetMapper\Command\ImportMapRequireCommand; +use Symfony\Component\AssetMapper\Command\ImportMapUpdateCommand; +use Symfony\Component\AssetMapper\Compiler\CssAssetUrlCompiler; +use Symfony\Component\AssetMapper\Compiler\JavaScriptImportPathCompiler; +use Symfony\Component\AssetMapper\Compiler\SourceMappingUrlsCompiler; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; +use Symfony\Component\AssetMapper\MapperAwareAssetPackage; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('asset_mapper', AssetMapper::class) + ->args([ + service('asset_mapper.repository'), + service('asset_mapper_compiler'), + param('kernel.project_dir'), + abstract_arg('asset public prefix'), + abstract_arg('public directory name'), + abstract_arg('extensions map'), + ]) + ->alias(AssetMapperInterface::class, 'asset_mapper') + ->set('asset_mapper.repository', AssetMapperRepository::class) + ->args([ + abstract_arg('array of asset mapper paths'), + param('kernel.project_dir'), + ]) + ->set('asset_mapper.asset_package', MapperAwareAssetPackage::class) + ->decorate('assets._default_package') + ->args([ + service('.inner'), + service('asset_mapper'), + ]) + + ->set('asset_mapper.dev_server_subscriber', AssetMapperDevServerSubscriber::class) + ->args([ + service('asset_mapper'), + ]) + ->tag('kernel.event_subscriber', ['event' => RequestEvent::class]) + + ->set('asset_mapper.command.compile', AssetMapperCompileCommand::class) + ->args([ + service('asset_mapper'), + service('asset_mapper.importmap.manager'), + service('filesystem'), + param('kernel.project_dir'), + abstract_arg('public directory name'), + param('kernel.debug'), + ]) + ->tag('console.command') + + ->set('asset_mapper_compiler', AssetMapperCompiler::class) + ->args([ + tagged_iterator('asset_mapper.compiler'), + ]) + + ->set('asset_mapper.compiler.css_asset_url_compiler', CssAssetUrlCompiler::class) + ->args([ + abstract_arg('strict mode'), + ]) + ->tag('asset_mapper.compiler') + + ->set('asset_mapper.compiler.source_mapping_urls_compiler', SourceMappingUrlsCompiler::class) + ->tag('asset_mapper.compiler') + + ->set('asset_mapper.compiler.javascript_import_path_compiler', JavaScriptImportPathCompiler::class) + ->args([ + abstract_arg('strict mode'), + ]) + ->tag('asset_mapper.compiler') + + ->set('asset_mapper.importmap.manager', ImportMapManager::class) + ->args([ + service('asset_mapper'), + abstract_arg('importmap.php path'), + abstract_arg('vendor directory'), + abstract_arg('provider'), + ]) + ->alias(ImportMapManager::class, 'asset_mapper.importmap.manager') + + ->set('asset_mapper.importmap.renderer', ImportMapRenderer::class) + ->args([ + service('asset_mapper.importmap.manager'), + param('kernel.charset'), + abstract_arg('polyfill URL'), + abstract_arg('script HTML attributes'), + ]) + + ->set('asset_mapper.importmap.command.require', ImportMapRequireCommand::class) + ->args([ + service('asset_mapper.importmap.manager'), + service('asset_mapper'), + ]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.remove', ImportMapRemoveCommand::class) + ->args([service('asset_mapper.importmap.manager')]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.update', ImportMapUpdateCommand::class) + ->args([service('asset_mapper.importmap.manager')]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.export', ImportMapExportCommand::class) + ->args([service('asset_mapper.importmap.manager')]) + ->tag('console.command') + ; +}; 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 33ac86560b756..52b1f158d4391 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 @@ -10,6 +10,7 @@ + @@ -186,6 +187,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php index efa9c7becab59..960735e884958 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php @@ -74,6 +74,7 @@ public function testSubscribedServices() 'security.token_storage' => '?Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface', 'security.csrf.token_manager' => '?Symfony\\Component\\Security\\Csrf\\CsrfTokenManagerInterface', 'web_link.http_header_serializer' => '?Symfony\\Component\\WebLink\\HttpHeaderSerializer', + 'asset_mapper.importmap.manager' => '?Symfony\\Component\\AssetMapper\\ImportMap\\ImportMapManager', ]; $this->assertEquals($expectedServices, $subscribed, 'Subscribed core services in AbstractController have changed'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index e11042d66e941..e91d32a68e74e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -95,6 +95,29 @@ public function testAssetsCanBeEnabled() $this->assertEquals($defaultConfig, $config['assets']); } + public function testAssetMapperCanBeEnabled() + { + $processor = new Processor(); + $configuration = new Configuration(true); + $config = $processor->processConfiguration($configuration, [['http_method_override' => false, 'asset_mapper' => null]]); + + $defaultConfig = [ + 'enabled' => true, + 'paths' => [], + 'server' => true, + 'public_prefix' => '/assets/', + 'strict_mode' => true, + 'extensions' => [], + 'importmap_path' => '%kernel.project_dir%/importmap.php', + 'importmap_polyfill' => null, + 'vendor_dir' => '%kernel.project_dir%/assets/vendor', + 'provider' => 'jspm', + 'importmap_script_attributes' => [], + ]; + + $this->assertEquals($defaultConfig, $config['asset_mapper']); + } + /** * @dataProvider provideValidAssetsPackageNameConfigurationTests */ @@ -589,6 +612,19 @@ protected static function getBundleDefaultConfig() 'json_manifest_path' => null, 'strict_mode' => false, ], + 'asset_mapper' => [ + 'enabled' => !class_exists(FullStack::class), + 'paths' => [], + 'server' => true, + 'public_prefix' => '/assets/', + 'strict_mode' => true, + 'extensions' => [], + 'importmap_path' => '%kernel.project_dir%/importmap.php', + 'importmap_polyfill' => null, + 'vendor_dir' => '%kernel.project_dir%/assets/vendor', + 'provider' => 'jspm', + 'importmap_script_attributes' => [], + ], 'cache' => [ 'pools' => [], 'app' => 'cache.adapter.filesystem', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/asset_mapper.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/asset_mapper.xml new file mode 100644 index 0000000000000..ebde46f585e2b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/asset_mapper.xml @@ -0,0 +1,24 @@ + + + + + + + assets/ + assets2/ + true + /assets_path/ + true + application/zip + %kernel.project_dir%/importmap.php + https://cdn.example.com/polyfill.js + reload + %kernel.project_dir%/assets/vendor + jspm + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php index 4a2ff788bf5c6..6b08cc19f712a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php @@ -14,7 +14,6 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; -use Symfony\Component\RateLimiter\Policy\SlidingWindowLimiter; class XmlFrameworkExtensionTest extends FrameworkExtensionTestCase { @@ -74,4 +73,19 @@ public function testRateLimiter() $this->assertTrue($container->hasDefinition('limiter.sliding_window')); } + + public function testAssetMapper() + { + $container = $this->createContainerFromFile('asset_mapper'); + + $definition = $container->getDefinition('asset_mapper'); + $this->assertSame('/assets_path/', $definition->getArgument(3)); + $this->assertSame(['zip' => 'application/zip'], $definition->getArgument(5)); + + $definition = $container->getDefinition('asset_mapper.importmap.renderer'); + $this->assertSame(['data-turbo-track' => 'reload'], $definition->getArgument(3)); + + $definition = $container->getDefinition('asset_mapper.repository'); + $this->assertSame(['assets/' => '', 'assets2/' => 'my_namespace'], $definition->getArgument(0)); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index e17a3a4c5cda9..eb3d340a71b96 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -36,6 +36,7 @@ "doctrine/annotations": "^1.13.1|^2", "doctrine/persistence": "^1.3|^2|^3", "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", "symfony/browser-kit": "^5.4|^6.0", "symfony/console": "^5.4.9|^6.0.9", "symfony/clock": "^6.2", diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index 5c3cff66fc411..63dd68e91b90d 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -46,6 +46,12 @@ public function process(ContainerBuilder $container) $container->removeDefinition('twig.extension.yaml'); } + if (!$container->has('asset_mapper')) { + // edge case where AssetMapper is installed, but not enabled + $container->removeDefinition('twig.extension.importmap'); + $container->removeDefinition('twig.runtime.importmap'); + } + $viewDir = \dirname((new \ReflectionClass(\Symfony\Bridge\Twig\Extension\FormExtension::class))->getFileName(), 2).'/Resources/views'; $templateIterator = $container->getDefinition('twig.template_iterator'); $templatePaths = $templateIterator->getArgument(1); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index 101dbf699a5a4..f257f60298bb6 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection; +use Symfony\Component\AssetMapper\AssetMapper; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\Console\Application; @@ -86,6 +87,10 @@ public function load(array $configs, ContainerBuilder $container) } } + if ($container::willBeAvailable('symfony/asset-mapper', AssetMapper::class, ['symfony/twig-bundle'])) { + $loader->load('importmap.php'); + } + $container->setParameter('twig.form.resources', $config['form_themes']); $container->setParameter('twig.default_path', $config['default_path']); $defaultTwigPath = $container->getParameterBag()->resolveValue($config['default_path']); diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/importmap.php b/src/Symfony/Bundle/TwigBundle/Resources/config/importmap.php new file mode 100644 index 0000000000000..c28021959e0f1 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/importmap.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Extension\ImportMapExtension; +use Symfony\Bridge\Twig\Extension\ImportMapRuntime; + +return static function (ContainerConfigurator $container) { + $container->services() + + ->set('twig.runtime.importmap', ImportMapRuntime::class) + ->args([service('asset_mapper.importmap.renderer')]) + ->tag('twig.runtime') + + ->set('twig.extension.importmap', ImportMapExtension::class) + ->tag('twig.extension') + ; +}; diff --git a/src/Symfony/Component/AssetMapper/.gitattributes b/src/Symfony/Component/AssetMapper/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/AssetMapper/.gitignore b/src/Symfony/Component/AssetMapper/.gitignore new file mode 100644 index 0000000000000..8e2a76cfcb804 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +phpunit.xml +Tests/fixtures/var/ diff --git a/src/Symfony/Component/AssetMapper/AssetDependency.php b/src/Symfony/Component/AssetMapper/AssetDependency.php new file mode 100644 index 0000000000000..17ae1a125a79d --- /dev/null +++ b/src/Symfony/Component/AssetMapper/AssetDependency.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper; + +/** + * Represents a dependency that a MappedAsset has. + * + * @experimental + */ +final class AssetDependency +{ + /** + * @param bool $isLazy whether this dependency is immediately needed + */ + public function __construct( + public readonly MappedAsset $asset, + public readonly bool $isLazy, + ) { + } +} diff --git a/src/Symfony/Component/AssetMapper/AssetMapper.php b/src/Symfony/Component/AssetMapper/AssetMapper.php new file mode 100644 index 0000000000000..f05348d2df68f --- /dev/null +++ b/src/Symfony/Component/AssetMapper/AssetMapper.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper; + +/** + * Finds and returns assets in the pipeline. + * + * @experimental + * + * @final + */ +class AssetMapper implements AssetMapperInterface +{ + public const MANIFEST_FILE_NAME = 'manifest.json'; + // source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + private const EXTENSIONS_MAP = [ + 'aac' => 'audio/aac', + 'abw' => 'application/x-abiword', + 'arc' => 'application/x-freearc', + 'avif' => 'image/avif', + 'avi' => 'video/x-msvideo', + 'azw' => 'application/vnd.amazon.ebook', + 'bin' => 'application/octet-stream', + 'bmp' => 'image/bmp', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'cda' => 'application/x-cdf', + 'csh' => 'application/x-csh', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'eot' => 'application/vnd.ms-fontobject', + 'epub' => 'application/epub+zip', + 'gz' => 'application/gzip', + 'gif' => 'image/gif', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/vnd.microsoft.icon', + 'ics' => 'text/calendar', + 'jar' => 'application/java-archive', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'jsonld' => 'application/ld+json', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mjs' => 'text/javascript', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mpeg' => 'video/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'opus' => 'audio/opus', + 'otf' => 'font/otf', + 'png' => 'image/png', + 'pdf' => 'application/pdf', + 'php' => 'application/x-httpd-php', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'rar' => 'application/vnd.rar', + 'rtf' => 'application/rtf', + 'sh' => 'application/x-sh', + 'svg' => 'image/svg+xml', + 'tar' => 'application/x-tar', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'ts' => 'video/mp2t', + 'ttf' => 'font/ttf', + 'txt' => 'text/plain', + 'vsd' => 'application/vnd.visio', + 'wav' => 'audio/wav', + 'weba' => 'audio/webm', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + ]; + private const PREDIGESTED_REGEX = '/-([0-9a-zA-Z]{7,128}\.digested)/'; + + private ?array $manifestData = null; + private array $fileContentsCache = []; + private array $assetsBeingCreated = []; + private readonly string $publicPrefix; + private array $extensionsMap = []; + + private array $assetsCache = []; + + public function __construct( + private readonly AssetMapperRepository $mapperRepository, + private readonly AssetMapperCompiler $compiler, + private readonly string $projectRootDir, + string $publicPrefix = '/assets/', + private readonly string $publicDirName = 'public', + array $extensionsMap = [], + ) { + // ensure that the public prefix always ends with a single slash + $this->publicPrefix = rtrim($publicPrefix, '/').'/'; + $this->extensionsMap = array_merge(self::EXTENSIONS_MAP, $extensionsMap); + } + + public function getPublicPrefix(): string + { + return $this->publicPrefix; + } + + public function getAsset(string $logicalPath): ?MappedAsset + { + if (\in_array($logicalPath, $this->assetsBeingCreated, true)) { + throw new \RuntimeException(sprintf('Circular reference detected while creating asset for "%s": "%s".', $logicalPath, implode(' -> ', $this->assetsBeingCreated).' -> '.$logicalPath)); + } + + if (!isset($this->assetsCache[$logicalPath])) { + $this->assetsBeingCreated[] = $logicalPath; + + $filePath = $this->mapperRepository->find($logicalPath); + if (null === $filePath) { + return null; + } + + $asset = new MappedAsset($logicalPath); + $this->assetsCache[$logicalPath] = $asset; + $asset->setSourcePath($filePath); + + $asset->setMimeType($this->getMimeType($logicalPath)); + $publicPath = $this->getPublicPath($logicalPath); + $asset->setPublicPath($publicPath); + [$digest, $isPredigested] = $this->getDigest($asset); + $asset->setDigest($digest, $isPredigested); + $asset->setContent($this->calculateContent($asset)); + + array_pop($this->assetsBeingCreated); + } + + return $this->assetsCache[$logicalPath]; + } + + /** + * @return MappedAsset[] + */ + public function allAssets(): array + { + $assets = []; + foreach ($this->mapperRepository->all() as $logicalPath => $filePath) { + $asset = $this->getAsset($logicalPath); + if (null === $asset) { + throw new \LogicException(sprintf('Asset "%s" could not be found.', $logicalPath)); + } + $assets[] = $asset; + } + + return $assets; + } + + public function getAssetFromSourcePath(string $sourcePath): ?MappedAsset + { + $logicalPath = $this->mapperRepository->findLogicalPath($sourcePath); + if (null === $logicalPath) { + return null; + } + + return $this->getAsset($logicalPath); + } + + public function getPublicPath(string $logicalPath): ?string + { + $manifestData = $this->loadManifest(); + if (isset($manifestData[$logicalPath])) { + return $manifestData[$logicalPath]; + } + + $filePath = $this->mapperRepository->find($logicalPath); + if (null === $filePath) { + return null; + } + + // grab the Asset - first look in the cache, as it may only be partially created + $asset = $this->assetsCache[$logicalPath] ?? $this->getAsset($logicalPath); + [$digest, $isPredigested] = $this->getDigest($asset); + + if ($isPredigested) { + return $this->publicPrefix.$logicalPath; + } + + return $this->publicPrefix.preg_replace_callback('/\.(\w+)$/', function ($matches) use ($digest) { + return "-{$digest}{$matches[0]}"; + }, $logicalPath); + } + + public static function isPathPredigested(string $path): bool + { + return 1 === preg_match(self::PREDIGESTED_REGEX, $path); + } + + public function getPublicAssetsFilesystemPath(): string + { + return rtrim(rtrim($this->projectRootDir, '/').'/'.$this->publicDirName.$this->publicPrefix, '/'); + } + + /** + * Returns an array of "string digest" and "bool predigested". + * + * @return array{0: string, 1: bool} + */ + private function getDigest(MappedAsset $asset): array + { + // check for a pre-digested file + if (1 === preg_match(self::PREDIGESTED_REGEX, $asset->logicalPath, $matches)) { + return [$matches[1], true]; + } + + return [ + hash('xxh128', $this->calculateContent($asset)), + false, + ]; + } + + private function getMimeType(string $logicalPath): ?string + { + $filePath = $this->mapperRepository->find($logicalPath); + if (null === $filePath) { + return null; + } + + $extension = pathinfo($logicalPath, \PATHINFO_EXTENSION); + + if (!isset($this->extensionsMap[$extension])) { + throw new \LogicException(sprintf('The file extension "%s" from "%s" does not correspond to any known types in the asset mapper. To support this extension, configure framework.asset_mapper.extensions.', $extension, $logicalPath)); + } + + return $this->extensionsMap[$extension]; + } + + private function calculateContent(MappedAsset $asset): string + { + if (isset($this->fileContentsCache[$asset->logicalPath])) { + return $this->fileContentsCache[$asset->logicalPath]; + } + + $content = file_get_contents($asset->getSourcePath()); + $content = $this->compiler->compile($content, $asset, $this); + + $this->fileContentsCache[$asset->logicalPath] = $content; + + return $content; + } + + private function loadManifest(): array + { + if (null === $this->manifestData) { + $path = $this->getPublicAssetsFilesystemPath().'/'.self::MANIFEST_FILE_NAME; + + if (!file_exists($path)) { + $this->manifestData = []; + } else { + $this->manifestData = json_decode(file_get_contents($path), true); + } + } + + return $this->manifestData; + } +} diff --git a/src/Symfony/Component/AssetMapper/AssetMapperCompiler.php b/src/Symfony/Component/AssetMapper/AssetMapperCompiler.php new file mode 100644 index 0000000000000..388095cc0a758 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/AssetMapperCompiler.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper; + +use Symfony\Component\AssetMapper\Compiler\AssetCompilerInterface; + +/** + * Runs a chain of compiles intended to adjust the source of assets. + * + * @experimental + * + * @final + */ +class AssetMapperCompiler +{ + /** + * @param iterable $assetCompilers + */ + public function __construct(private iterable $assetCompilers) + { + } + + public function compile(string $content, MappedAsset $mappedAsset, AssetMapperInterface $assetMapper): string + { + foreach ($this->assetCompilers as $compiler) { + if (!$compiler->supports($mappedAsset)) { + continue; + } + + $content = $compiler->compile($content, $mappedAsset, $assetMapper); + } + + return $content; + } +} diff --git a/src/Symfony/Component/AssetMapper/AssetMapperDevServerSubscriber.php b/src/Symfony/Component/AssetMapper/AssetMapperDevServerSubscriber.php new file mode 100644 index 0000000000000..e7ba6f2ff4bc7 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/AssetMapperDevServerSubscriber.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Functions like a controller that returns assets from the asset mapper. + * + * @experimental + * + * @author Ryan Weaver + */ +final class AssetMapperDevServerSubscriber implements EventSubscriberInterface +{ + public function __construct( + private readonly AssetMapperInterface $assetMapper, + ) { + } + + public function onKernelRequest(RequestEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + $pathInfo = $event->getRequest()->getPathInfo(); + if (!str_starts_with($pathInfo, $this->assetMapper->getPublicPrefix())) { + return; + } + + [$assetPath, $digest] = $this->extractAssetPathAndDigest($pathInfo); + $asset = $this->assetMapper->getAsset($assetPath); + + if (!$asset) { + throw new NotFoundHttpException(sprintf('Asset "%s" not found.', $assetPath)); + } + + if ($asset->getDigest() !== $digest) { + throw new NotFoundHttpException(sprintf('Asset "%s" was found but the digest does not match.', $assetPath)); + } + + $response = (new Response( + $asset->getContent(), + headers: $asset->getMimeType() ? ['Content-Type' => $asset->getMimeType()] : [], + )) + ->setPublic() + ->setMaxAge(604800) + ->setImmutable() + ->setEtag($asset->getDigest()) + ; + + $event->setResponse($response); + } + + public static function getSubscribedEvents(): array + { + return [ + // priority higher than RouterListener + KernelEvents::REQUEST => [['onKernelRequest', 35]], + ]; + } + + private function extractAssetPathAndDigest(string $fullPath): array + { + $fullPath = substr($fullPath, \strlen($this->assetMapper->getPublicPrefix())); + preg_match('/-([0-9a-zA-Z]{7,128}(?:\.digested)?)\.[^.]+\z/', $fullPath, $matches); + + if (!isset($matches[1])) { + return [$fullPath, null]; + } + + $digest = $matches[1]; + + $path = AssetMapper::isPathPredigested($fullPath) ? $fullPath : str_replace("-{$digest}", '', $fullPath); + + return [$path, $digest]; + } +} diff --git a/src/Symfony/Component/AssetMapper/AssetMapperInterface.php b/src/Symfony/Component/AssetMapper/AssetMapperInterface.php new file mode 100644 index 0000000000000..0acd886d6dc1a --- /dev/null +++ b/src/Symfony/Component/AssetMapper/AssetMapperInterface.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper; + +/** + * Finds and returns assets in the pipeline. + * + * @experimental + * + * @author Ryan Weaver + */ +interface AssetMapperInterface +{ + /** + * The path that should be prefixed on all asset paths to point to the output location. + */ + public function getPublicPrefix(): string; + + /** + * Given the logical path (e.g. path relative to a mapped directory), return the asset. + */ + public function getAsset(string $logicalPath): ?MappedAsset; + + /** + * Returns all mapped assets. + * + * @return MappedAsset[] + */ + public function allAssets(): array; + + /** + * Fetches the asset given its source path (i.e. filesystem path). + */ + public function getAssetFromSourcePath(string $sourcePath): ?MappedAsset; + + /** + * Returns the public path for this asset, if it can be found. + */ + public function getPublicPath(string $logicalPath): ?string; + + /** + * Returns the filesystem path to where assets are stored when compiled. + */ + public function getPublicAssetsFilesystemPath(): string; +} diff --git a/src/Symfony/Component/AssetMapper/AssetMapperRepository.php b/src/Symfony/Component/AssetMapper/AssetMapperRepository.php new file mode 100644 index 0000000000000..87c165ccf23e3 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/AssetMapperRepository.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator; + +/** + * Finds assets in the asset mapper. + * + * @experimental + * + * @author Ryan Weaver + * + * @final + */ +class AssetMapperRepository +{ + private ?array $absolutePaths = null; + + /** + * @param string[] $paths Array of assets paths: key is the path, value is the namespace + * (empty string for no namespace) + */ + public function __construct( + private readonly array $paths, + private readonly string $projectRootDir + ) { + } + + /** + * Given the logical path - styles/app.css - returns the absolute path to the file. + */ + public function find(string $logicalPath): ?string + { + foreach ($this->getDirectories() as $path => $namespace) { + $localLogicalPath = $logicalPath; + // if this path has a namespace, only look for files in that namespace + if ('' !== $namespace) { + if (!str_starts_with($logicalPath, rtrim($namespace, '/').'/')) { + continue; + } + + $localLogicalPath = substr($logicalPath, \strlen($namespace) + 1); + } + + $file = rtrim($path, '/').'/'.$localLogicalPath; + if (file_exists($file)) { + return $file; + } + } + + return null; + } + + public function findLogicalPath(string $filesystemPath): ?string + { + foreach ($this->getDirectories() as $path => $namespace) { + if (!str_starts_with($filesystemPath, $path)) { + continue; + } + + $logicalPath = substr($filesystemPath, \strlen($path)); + if ('' !== $namespace) { + $logicalPath = $namespace.'/'.$logicalPath; + } + + return ltrim($logicalPath, '/'); + } + + return null; + } + + /** + * Returns an array of all files in the asset_mapper. + * + * Key is the logical path, value is the absolute path. + * + * @return string[] + */ + public function all(): array + { + $paths = []; + foreach ($this->getDirectories() as $path => $namespace) { + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); + foreach ($iterator as $file) { + if (!$file->isFile()) { + continue; + } + + /** @var RecursiveDirectoryIterator $innerIterator */ + $innerIterator = $iterator->getInnerIterator(); + $logicalPath = ($namespace ? rtrim($namespace, '/').'/' : '').$innerIterator->getSubPathName(); + $paths[$logicalPath] = $file->getPathname(); + } + } + + return $paths; + } + + private function getDirectories(): array + { + $filesystem = new Filesystem(); + if (null !== $this->absolutePaths) { + return $this->absolutePaths; + } + + $this->absolutePaths = []; + foreach ($this->paths as $path => $namespace) { + if ($filesystem->isAbsolutePath($path)) { + if (!file_exists($path)) { + throw new \InvalidArgumentException(sprintf('The asset mapper directory "%s" does not exist.', $path)); + } + $this->absolutePaths[$path] = $namespace; + + continue; + } + + if (file_exists($this->projectRootDir.'/'.$path)) { + $this->absolutePaths[$this->projectRootDir.'/'.$path] = $namespace; + + continue; + } + + throw new \InvalidArgumentException(sprintf('The asset mapper directory "%s" does not exist.', $path)); + } + + return $this->absolutePaths; + } +} diff --git a/src/Symfony/Component/AssetMapper/CHANGELOG.md b/src/Symfony/Component/AssetMapper/CHANGELOG.md new file mode 100644 index 0000000000000..f5a3d015eac64 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + + * Add the component as experimental diff --git a/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php b/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php new file mode 100644 index 0000000000000..6c08da1c7d68b --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Command; + +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Filesystem\Filesystem; + +/** + * Compiles the assets in the asset mapper to the final output directory. + * + * This command is intended to be used during deployment. + * + * @experimental + * + * @author Ryan Weaver + */ +#[AsCommand(name: 'assetmap:compile', description: 'Compiles all mapped assets and writes them to the final public output directory.')] +final class AssetMapperCompileCommand extends Command +{ + public function __construct( + private readonly AssetMapperInterface $assetMapper, + private readonly ImportMapManager $importMapManager, + private readonly Filesystem $filesystem, + private readonly string $projectDir, + private readonly string $publicDirName, + private readonly bool $isDebug, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addOption('clean', null, null, 'Whether to clean the public directory before compiling assets') + ->setHelp(<<<'EOT' +The %command.name% command compiles and dumps all the assets in +the asset mapper into the final public directory (usually public/assets). + +This command is meant to be run during deployment. +EOT + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $publicDir = $this->projectDir.'/'.$this->publicDirName; + if (!is_dir($publicDir)) { + throw new InvalidArgumentException(sprintf('The public directory "%s" does not exist.', $publicDir)); + } + + if ($input->getOption('clean')) { + $outputDir = $publicDir.$this->assetMapper->getPublicPrefix(); + $io->comment(sprintf('Cleaning %s', $outputDir)); + $this->filesystem->remove($outputDir); + $this->filesystem->mkdir($outputDir); + } + + $allAssets = $this->assetMapper->allAssets(); + + $io->comment(sprintf('Compiling %d assets to %s%s', \count($allAssets), $publicDir, $this->assetMapper->getPublicPrefix())); + $manifest = []; + foreach ($allAssets as $asset) { + // $asset->getPublicPath() will start with a "/" + $targetPath = $publicDir.$asset->getPublicPath(); + + if (!is_dir($dir = \dirname($targetPath))) { + $this->filesystem->mkdir($dir); + } + + $this->filesystem->dumpFile($targetPath, $asset->getContent()); + $manifest[$asset->logicalPath] = $asset->getPublicPath(); + } + + $manifestPath = $publicDir.$this->assetMapper->getPublicPrefix().AssetMapper::MANIFEST_FILE_NAME; + $this->filesystem->dumpFile($manifestPath, json_encode($manifest, \JSON_PRETTY_PRINT)); + $io->comment(sprintf('Manifest written to %s', $manifestPath)); + + $importMapPath = $publicDir.$this->assetMapper->getPublicPrefix().ImportMapManager::IMPORT_MAP_FILE_NAME; + $this->filesystem->dumpFile($importMapPath, $this->importMapManager->getImportMapJson()); + $io->comment(sprintf('Import map written to %s', $importMapPath)); + + if ($this->isDebug) { + $io->warning(sprintf( + 'You are compiling assets in development. Symfony will not serve any changed assets until you delete %s and %s.', + $manifestPath, + $importMapPath + )); + } + + return self::SUCCESS; + } +} diff --git a/src/Symfony/Component/AssetMapper/Command/ImportMapExportCommand.php b/src/Symfony/Component/AssetMapper/Command/ImportMapExportCommand.php new file mode 100644 index 0000000000000..4a3af6e18f93d --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Command/ImportMapExportCommand.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Command; + +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @experimental + * + * @author Kévin Dunglas + */ +#[AsCommand(name: 'importmap:export', description: 'Exports the importmap JSON')] +final class ImportMapExportCommand extends Command +{ + public function __construct( + private readonly ImportMapManager $importMapManager, + ) { + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln($this->importMapManager->getImportMapJson()); + + return Command::SUCCESS; + } +} diff --git a/src/Symfony/Component/AssetMapper/Command/ImportMapRemoveCommand.php b/src/Symfony/Component/AssetMapper/Command/ImportMapRemoveCommand.php new file mode 100644 index 0000000000000..cec830e2ece06 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Command/ImportMapRemoveCommand.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Command; + +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @experimental + * + * @author Kévin Dunglas + */ +#[AsCommand(name: 'importmap:remove', description: 'Removes JavaScript packages')] +final class ImportMapRemoveCommand extends Command +{ + public function __construct( + protected readonly ImportMapManager $importMapManager, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The packages to remove'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $packageList = $input->getArgument('packages'); + $this->importMapManager->remove($packageList); + + if (1 === \count($packageList)) { + $io->success(sprintf('Removed "%s" from importmap.php.', $packageList[0])); + } else { + $io->success(sprintf('Removed %d items from importmap.php.', \count($packageList))); + } + + return Command::SUCCESS; + } +} diff --git a/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php b/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php new file mode 100644 index 0000000000000..529a90bca7f57 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Command/ImportMapRequireCommand.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Command; + +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\AssetMapper\ImportMap\PackageRequireOptions; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @experimental + * + * @author Kévin Dunglas + */ +#[AsCommand(name: 'importma 10000 p:require', description: 'Requires JavaScript packages')] +final class ImportMapRequireCommand extends Command +{ + public function __construct( + private readonly ImportMapManager $importMapManager, + private readonly AssetMapperInterface $assetMapper, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The packages to add'); + $this->addOption('download', 'd', InputOption::VALUE_NONE, 'Download packages locally'); + $this->addOption('preload', 'p', InputOption::VALUE_NONE, 'Preload packages'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $packageList = $input->getArgument('packages'); + if ($input->hasOption('path') && \count($packageList) > 1) { + $io->error('The "--path" option can only be used when you require a single package.'); + + return Command::FAILURE; + } + + $packages = []; + foreach ($packageList as $packageName) { + $parts = ImportMapManager::parsePackageName($packageName); + if (null === $parts) { + $io->error(sprintf('Package "%s" is not a valid package name format. Use the format PACKAGE@VERSION - e.g. "lodash" or "lodash@^4"', $packageName)); + + return Command::FAILURE; + } + + $packages[] = new PackageRequireOptions( + $parts['package'], + $parts['version'] ?? null, + $input->getOption('download'), + $input->getOption('preload'), + null, + isset($parts['registry']) && $parts['registry'] ? $parts['registry'] : null, + ); + } + + $newPackages = $this->importMapManager->require($packages); + if (1 === \count($newPackages)) { + $newPackage = $newPackages[0]; + $message = sprintf('Package "%s" added to importmap.php', $newPackage->importName); + + if ($newPackage->isDownloaded && null !== $downloadedAsset = $this->assetMapper->getAsset($newPackage->path)) { + $application = $this->getApplication(); + if ($application instanceof Application) { + $projectDir = $application->getKernel()->getProjectDir(); + $downloadedPath = $downloadedAsset->getSourcePath(); + if (str_starts_with($downloadedPath, $projectDir)) { + $downloadedPath = substr($downloadedPath, \strlen($projectDir) + 1); + } + + $message .= sprintf(' and downloaded locally to "%s"', $downloadedPath); + } + } + + $message .= '.'; + } else { + $message = sprintf('%d new packages (%s) added to the importmap.php!', \count($newPackages), implode(', ', array_keys($newPackages))); + } + + $messages = [$message]; + + if (1 === \count($newPackages)) { + $messages[] = sprintf('Use the new package normally by importing "%s".', $newPackages[0]->importName); + } + + $io->success($messages); + + return Command::SUCCESS; + } +} diff --git a/src/Symfony/Component/AssetMapper/Command/ImportMapUpdateCommand.php b/src/Symfony/Component/AssetMapper/Command/ImportMapUpdateCommand.php new file mode 100644 index 0000000000000..97559699d06f4 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Command/ImportMapUpdateCommand.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Command; + +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @experimental + * + * @author Kévin Dunglas + */ +#[AsCommand(name: 'importmap:update', description: 'Updates all JavaScript packages to their latest versions')] +final class ImportMapUpdateCommand extends Command +{ + public function __construct( + protected readonly ImportMapManager $importMapManager, + ) { + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $this->importMapManager->update(); + + $io->success('Updated all packages in importmap.php.'); + + return Command::SUCCESS; + } +} diff --git a/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerInterface.php b/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerInterface.php new file mode 100644 index 0000000000000..247161dffa329 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Compiler; + +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\MappedAsset; + +/** + * An asset compiler is responsible for applying any changes to the contents of an asset. + * + * @experimental + * + * @author Ryan Weaver + */ +interface AssetCompilerInterface +{ + public function supports(MappedAsset $asset): bool; + + /** + * Applies any changes to the contents of the asset. + */ + public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string; +} diff --git a/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerPathResolverTrait.php b/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerPathResolverTrait.php new file mode 100644 index 0000000000000..e40e827548934 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Compiler/AssetCompilerPathResolverTrait.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Compiler; + +use Symfony\Component\Asset\Exception\RuntimeException; + +/** + * Helps resolve "../" and "./" in paths. + * + * @experimental + * + * @internal + */ +trait AssetCompilerPathResolverTrait +{ + private function resolvePath(string $directory, string $filename): string + { + $pathParts = array_filter(explode('/', $directory.'/'.$filename)); + $output = []; + + foreach ($pathParts as $part) { + if ('..' === $part) { + if (0 === \count($output)) { + throw new RuntimeException(sprintf('Cannot import the file "%s": it is outside the current "%s" directory.', $filename, $directory)); + } + + array_pop($output); + continue; + } + + if ('.' === $part) { + // skip + continue; + } + + $output[] = $part; + } + + return implode('/', $output); + } +} diff --git a/src/Symfony/Component/AssetMapper/Compiler/CssAssetUrlCompiler.php b/src/Symfony/Component/AssetMapper/Compiler/CssAssetUrlCompiler.php new file mode 100644 index 0000000000000..0ba3e650ef361 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Compiler/CssAssetUrlCompiler.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Compiler; + +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\MappedAsset; + +/** + * Resolves url() paths in CSS files. + * + * Originally sourced from https://github.com/rails/propshaft/blob/main/lib/propshaft/compilers/css_asset_urls.rb + * + * @experimental + */ +final class CssAssetUrlCompiler implements AssetCompilerInterface +{ + use AssetCompilerPathResolverTrait; + + // https://regex101.com/r/BOJ3vG/1 + public const ASSET_URL_PATTERN = '/url\(\s*["\']?(?!(?:\/|\#|%23|data|http|\/\/))([^"\'\s?#)]+)([#?][^"\')]+)?\s*["\']?\)/'; + + public function __construct(private readonly bool $strictMode = true) + { + } + + public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string + { + return preg_replace_callback(self::ASSET_URL_PATTERN, function ($matches) use ($asset, $assetMapper) { + $resolvedPath = $this->resolvePath(\dirname($asset->logicalPath), $matches[1]); + $dependentAsset = $assetMapper->getAsset($resolvedPath); + + if (null === $dependentAsset) { + if ($this->strictMode) { + throw new \RuntimeException(sprintf('Unable to find asset "%s" referenced in "%s".', $resolvedPath, $asset->getSourcePath())); + } + + // return original, unchanged path + return $matches[0]; + } + + $asset->addDependency($dependentAsset); + + return 'url("'.$dependentAsset->getPublicPath().'")'; + }, $content); + } + + public function supports(MappedAsset $asset): bool + { + return 'text/css' === $asset->getMimeType(); + } +} diff --git a/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php b/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php new file mode 100644 index 0000000000000..f252bb7bb9abe --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Compiler/JavaScriptImportPathCompiler.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Compiler; + +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\MappedAsset; + +/** + * Resolves import paths in JS files. + * + * @experimental + * + * @author Ryan Weaver + */ +final class JavaScriptImportPathCompiler implements AssetCompilerInterface +{ + use AssetCompilerPathResolverTrait; + + // https://regex101.com/r/VFdR4H/1 + private const IMPORT_PATTERN = '/(?:import\s+(?:(?:\*\s+as\s+\w+|[\w\s{},*]+)\s+from\s+)?|\bimport\()\s*[\'"`](\.\/[^\'"`]+|(\.\.\/)+[^\'"`]+)[\'"`]\s*[;\)]?/m'; + + public function __construct(private readonly bool $strictMode = true) + { + } + + public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string + { + return preg_replace_callback(self::IMPORT_PATTERN, function ($matches) use ($asset, $assetMapper) { + $resolvedPath = $this->resolvePath(\dirname($asset->logicalPath), $matches[1]); + + $dependentAsset = $assetMapper->getAsset($resolvedPath); + + if (!$dependentAsset && $this->strictMode) { + $message = sprintf('Unable to find asset "%s" imported from "%s".', $resolvedPath, $asset->getSourcePath()); + + if (null !== $assetMapper->getAsset(sprintf('%s.js', $resolvedPath))) { + $message .= sprintf(' Try adding ".js" to the end of the import - i.e. "%s.js".', $resolvedPath); + } + + throw new \RuntimeException($message); + } + + if ($dependentAsset && $this->supports($dependentAsset)) { + // If we found the path and it's a JavaScript file, list it as a dependency. + // This will cause the asset to be included in the importmap. + $isLazy = str_contains($matches[0], 'import('); + + $asset->addDependency($dependentAsset, $isLazy); + } + + return $matches[0]; + }, $content); + } + + public function supports(MappedAsset $asset): bool + { + return 'application/javascript' === $asset->getMimeType() || 'text/javascript' === $asset->getMimeType(); + } +} diff --git a/src/Symfony/Component/AssetMapper/Compiler/SourceMappingUrlsCompiler.php b/src/Symfony/Component/AssetMapper/Compiler/SourceMappingUrlsCompiler.php new file mode 100644 index 0000000000000..31dab5a79c7e2 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Compiler/SourceMappingUrlsCompiler.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Compiler; + +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\MappedAsset; + +/** + * Rewrites already-existing source map URLs to their final digested path. + * + * Originally sourced from https://github.com/rails/propshaft/blob/main/lib/propshaft/compilers/source_mapping_urls.rb + * + * @experimental + */ +final class SourceMappingUrlsCompiler implements AssetCompilerInterface +{ + use AssetCompilerPathResolverTrait; + + private const SOURCE_MAPPING_PATTERN = '/^(\/\/|\/\*)# sourceMappingURL=(.+\.map)/m'; + + public function supports(MappedAsset $asset): bool + { + return \in_array($asset->getMimeType(), ['application/javascript', 'text/css'], true); + } + + public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string + { + return preg_replace_callback(self::SOURCE_MAPPING_PATTERN, function ($matches) use ($asset, $assetMapper) { + $resolvedPath = $this->resolvePath(\dirname($asset->logicalPath), $matches[2]); + + $dependentAsset = $assetMapper->getAsset($resolvedPath); + if (!$dependentAsset) { + // return original, unchanged path + return $matches[0]; + } + + $asset->addDependency($dependentAsset); + + return $matches[1].'# sourceMappingURL='.$dependentAsset->getPublicPath(); + }, $content); + } +} diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapEntry.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapEntry.php new file mode 100644 index 0000000000000..ad31b99427ee4 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapEntry.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\ImportMap; + +/** + * Represents an item that should be in the importmap. + * + * @experimental + * + * @author Ryan Weaver + */ +final class ImportMapEntry +{ + public function __construct( + /** + * The logical path to this asset if local or downloaded. + */ + public readonly string $importName, + public readonly ?string $path = null, + public readonly ?string $url = null, + public readonly bool $isDownloaded = false, + public readonly bool $preload = false, + ) { + } +} diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php new file mode 100644 index 0000000000000..1dbbf34d28d92 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapManager.php @@ -0,0 +1,399 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\ImportMap; + +use Symfony\Component\AssetMapper\AssetDependency; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\VarExporter\VarExporter; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @experimental + * + * @author Kévin Dunglas + * @author Ryan Weaver + * @final + */ +class ImportMapManager +{ + public const PROVIDER_JSPM = 'jspm'; + public const PROVIDER_JSPM_SYSTEM = 'jspm.system'; + public const PROVIDER_SKYPACK = 'skypack'; + public const PROVIDER_JSDELIVR = 'jsdelivr'; + public const PROVIDER_UNPKG = 'unpkg'; + public const PROVIDERS = [ + self::PROVIDER_JSPM, + self::PROVIDER_JSPM_SYSTEM, + self::PROVIDER_SKYPACK, + self::PROVIDER_JSDELIVR, + self::PROVIDER_UNPKG, + ]; + + public const POLYFILL_URL = 'https://ga.jspm.io/npm:es-module-shims@1.7.2/dist/es-module-shims.js'; + + /** + * @see https://regex101.com/r/2cR9Rh/1 + * + * Partially based on https://github.com/dword-design/package-name-regex + */ + private const PACKAGE_PATTERN = '/^(?:https?:\/\/[\w\.-]+\/)?(?:(?\w+):)?(?(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*)(?:@(?[\w\._-]+))?(?:(?\/.*))?$/'; + public const IMPORT_MAP_FILE_NAME = 'importmap.json'; + + private array $importMapEntries; + private array $modulesToPreload; + private string $json; + + public function __construct( + private readonly AssetMapperInterface $assetMapper, + private readonly string $importMapConfigPath, + private readonly string $vendorDir, + private readonly string $provider = self::PROVIDER_JSPM, + private ?HttpClientInterface $httpClient = null, + ) { + $this->httpClient = $httpClient ?? HttpClient::create(['base_uri' => 'https://api.jspm.io/']); + } + + public function getModulesToPreload(): array + { + $this->buildImportMapJson(); + + return $this->modulesToPreload; + } + + public function getImportMapJson(): string + { + $this->buildImportMapJson(); + + return $this->json; + } + + /** + * Adds or updates packages. + * + * @param PackageRequireOptions[] $packages + * @return ImportMapEntry[] + */ + public function require(array $packages): array + { + return $this->updateImportMapConfig(false, $packages, []); + } + + /** + * Removes packages. + * + * @param string[] $packages + */ + public function remove(array $packages): void + { + $this->updateImportMapConfig(false, [], $packages); + } + + /** + * Updates all existing packages to the latest version. + */ + public function update(): array + { + return $this->updateImportMapConfig(true, [], []); + } + + /** + * @internal + */ + public static function parsePackageName(string $packageName): ?array + { + // https://regex101.com/r/58bl9L/1 + $regex = '/(?:(?P[^:\n]+):)?(?P[^@\n]+)(?:@(?P[^\s\n]+))?/'; + + return preg_match($regex, $packageName, $matches) ? $matches : null; + } + + private function buildImportMapJson(): void + { + if (isset($this->json)) { + return; + } + + $dumpedPath = $this->assetMapper->getPublicAssetsFilesystemPath().'/'.self::IMPORT_MAP_FILE_NAME; + if (file_exists($dumpedPath)) { + $this->json = file_get_contents($dumpedPath); + + return; + } + + $entries = $this->loadImportMapEntries(); + $this->modulesToPreload = []; + + $imports = $this->convertEntriesToImports($entries); + + $importmap['imports'] = $imports; + + // Use JSON_UNESCAPED_SLASHES | JSON_HEX_TAG to prevent XSS + $this->json = json_encode($importmap, \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG); + } + + /** + * @param PackageRequireOptions[] $packagesToRequire + * @param string[] $packagesToRemove + * @return ImportMapEntry[] + */ + private function updateImportMapConfig(bool $update, array $packagesToRequire, array $packagesToRemove): array + { + $currentEntries = $this->loadImportMapEntries(); + + foreach ($packagesToRemove as $packageName) { + if (!isset($currentEntries[$packageName])) { + throw new \InvalidArgumentException(sprintf('Package "%s" listed for removal was not found in "%s".', $packageName, basename($this->importMapConfigPath))); + } + + $this->cleanupPackageFiles($currentEntries[$packageName]); + unset($currentEntries[$packageName]); + } + + if ($update) { + foreach ($currentEntries as $importName => $entry) { + if (null === $entry->url) { + continue; + } + + // assume the import name === package name, unless we can parse + // the true package name from the URL + $packageName = $importName; + $registry = null; + + // try to grab the package name & jspm "registry" from the URL + if (str_starts_with($entry->url, 'https://ga.jspm.io') && 1 === preg_match(self::PACKAGE_PATTERN, $entry->url, $matches)) { + $packageName = $matches['package']; + $registry = $matches['registry'] ?? null; + } + + $packagesToRequire[] = new PackageRequireOptions( + $packageName, + null, + $entry->isDownloaded, + $entry->preload, + $importName, + $registry, + ); + + // remove it: then it will be re-added + $this->cleanupPackageFiles($entry); + unset($currentEntries[$importName]); + } + } + + $newEntries = $this->requirePackages($packagesToRequire, $currentEntries); + $this->writeImportMapConfig($currentEntries); + + return $newEntries; + } + + /** + * Gets information about (and optionally downloads) the packages & updates the entries. + * + * Returns an array of the entries that were added. + * + * @param PackageRequireOptions[] $packagesToRequire + * @param array $importMapEntries + */ + private function requirePackages(array $packagesToRequire, array &$importMapEntries): array + { + if (!$packagesToRequire) { + return []; + } + + $installData = []; + $packageRequiresByName = []; + foreach ($packagesToRequire as $requireOptions) { + $constraint = $requireOptions->packageName; + if (null !== $requireOptions->versionConstraint) { + $constraint .= '@' . $requireOptions->versionConstraint; + } + if (null !== $requireOptions->registryName) { + $constraint = sprintf('%s:%s', $requireOptions->registryName, $constraint); + } + $installData[] = $constraint; + $packageRequiresByName[$requireOptions->packageName] = $requireOptions; + } + + $json = [ + 'install' => $installData, + 'flattenScope' => true, + // always grab production-ready assets + 'env' => ['browser', 'module', 'production'], + ]; + if (self::PROVIDER_JSPM !== $this->provider) { + $json['provider'] = $this->provider; + } + + $response = $this->httpClient->request('POST', 'generate', [ + 'json' => $json, + ]); + + if (200 !== $response->getStatusCode()) { + $data = $response->toArray(false); + + if (isset($data['error'])) { + throw new \RuntimeException(sprintf('Error requiring JavaScript package: "%s"', $data['error'])); + } + + // Throws the original HttpClient exception + $response->getHeaders(); + } + + // if we're requiring just one package, in case it has any peer deps, match the preload + $defaultPreload = 1 === count($packagesToRequire) ? $packagesToRequire[0]->preload : false; + + $addedEntries = []; + foreach ($response->toArray()['map']['imports'] as $packageName => $url) { + $requireOptions = $packageRequiresByName[$packageName] ?? null; + $importName = $requireOptions && $requireOptions->importName ? $requireOptions->importName : $packageName; + $preload = $requireOptions ? $requireOptions->preload : $defaultPreload; + $download = $requireOptions ? $requireOptions->download : false; + $path = null; + + if ($download) { + $vendorPath = $this->vendorDir.'/'.$packageName.'.js'; + + @mkdir(\dirname($vendorPath), 0777, true); + file_put_contents($vendorPath, $this->httpClient->request('GET', $url)->getContent()); + + $mappedAsset = $this->assetMapper->getAssetFromSourcePath($vendorPath); + if (null === $mappedAsset) { + unlink($vendorPath); + + throw new \LogicException(sprintf('The package was downloaded to "%s", but this path does not appear to be in any of your asset paths.', $vendorPath)); + } + $path = $mappedAsset->logicalPath; + } + + $newEntry = new ImportMapEntry($importName, $path, $url, $download, $preload); + $importMapEntries[$importName] = $newEntry; + $addedEntries[] = $newEntry; + } + + return $addedEntries; + } + + private function cleanupPackageFiles(ImportMapEntry $entry): void + { + if (null === $entry->path) { + return; + } + + $asset = $this->assetMapper->getAsset($entry->path); + + if (is_file($asset->getSourcePath())) { + @unlink($asset->getSourcePath()); + } + } + + /** + * @return array + */ + private function loadImportMapEntries(): array + { + if (isset($this->importMapEntries)) { + return $this->importMapEntries; + } + + $path = $this->importMapConfigPath; + $importMapConfig = is_file($path) ? (static fn () => include $path)() : []; + + $entries = []; + foreach ($importMapConfig ?? [] as $importName => $data) { + $entries[$importName] = new ImportMapEntry( + $importName, + path: $data['path'] ?? $data['downloaded_to'] ?? null, + url: $data['url'] ?? null, + isDownloaded: isset($data['downloaded_to']), + preload: $data['preload'] ?? false, + ); + } + + return $this->importMapEntries = $entries; + } + + /** + * @param ImportMapEntry[] $entries + */ + private function writeImportMapConfig(array $entries): void + { + $this->importMapEntries = $entries; + unset($this->modulesToPreload); + unset($this->json); + + $importMapConfig = []; + foreach ($entries as $entry) { + $config = []; + if ($entry->path) { + $config[$entry->isDownloaded ? 'downloaded_to' : 'path'] = $entry->path; + } + if ($entry->url) { + $config['url'] = $entry->url; + } + if ($entry->preload) { + $config['preload'] = $entry->preload; + } + $importMapConfig[$entry->importName] = $config; + } + + $map = class_exists(VarExporter::class) ? VarExporter::export($importMapConfig) : var_export($importMapConfig, true); + file_put_contents($this->importMapConfigPath, "importName])) { + continue; + } + + $dependencies = []; + + if (null !== $entryOptions->path) { + $asset = $this->assetMapper->getAsset($entryOptions->path); + if (!$asset) { + throw new \InvalidArgumentException(sprintf('The asset "%s" mentioned in "%s" cannot be found in any asset map paths.', $entryOptions->path, basename($this->importMapConfigPath))); + } + $path = $asset->getPublicPath(); + $dependencies = $asset->getDependencies(); + } elseif (null !== $entryOptions->url) { + $path = $entryOptions->url; + } else { + throw new \InvalidArgumentException(sprintf('The package "%s" mentioned in "%s" must have a "path" or "url" key.', $entryOptions->importName, basename($this->importMapConfigPath))); + } + + $imports[$entryOptions->importName] = $path; + + if ($entryOptions->preload ?? false) { + $this->modulesToPreload[] = $path; + } + + $dependencyImportMapEntries = array_map(function (AssetDependency $dependency) { + return new ImportMapEntry( + $dependency->asset->getPublicPathWithoutDigest(), + $dependency->asset->logicalPath, + preload: !$dependency->isLazy, + ); + }, $dependencies); + $imports = array_merge($imports, $this->convertEntriesToImports($dependencyImportMapEntries)); + } + + return $imports; + } +} diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapRenderer.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapRenderer.php new file mode 100644 index 0000000000000..08c75f52771ba --- /dev/null +++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapRenderer.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\ImportMap; + +/** + * @experimental + * + * @author Kévin Dunglas + * @author Ryan Weaver + * @final + */ +class ImportMapRenderer +{ + public function __construct( + private readonly ImportMapManager $importMapManager, + private readonly string $charset = 'UTF-8', + private readonly string|false $polyfillUrl = ImportMapManager::POLYFILL_URL, + private readonly array $scriptAttributes = [], + ) { + } + + public function render(?string $entryPoint = null): string + { + $attributeString = ''; + + if (isset($this->scriptAttributes['src']) || isset($this->scriptAttributes['type'])) { + throw new \InvalidArgumentException(sprintf('The "src" and "type" attributes are not allowed on the + HTML; + + if ($this->polyfillUrl) { + $url = $this->escapeAttributeValue($this->polyfillUrl); + + $output .= << + + HTML; + } + + foreach ($this->importMapManager->getModulesToPreload() as $url) { + $url = $this->escapeAttributeValue($url); + + $output .= "\n"; + } + + if (null !== $entryPoint) { + $output .= "\n"; + } + + return $output; + } + + private function escapeAttributeValue(string $value): string + { + return htmlspecialchars($value, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); + } +} diff --git a/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php b/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php new file mode 100644 index 0000000000000..6d44efede25fb --- /dev/null +++ b/src/Symfony/Component/AssetMapper/ImportMap/PackageRequireOptions.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\ImportMap; + +/** + * Represents a package that should be installed or updated. + * + * @experimental + * + * @author Kévin Dunglas + */ +final class PackageRequireOptions +{ + public function __construct( + public readonly string $packageName, + public readonly ?string $versionConstraint = null, + public readonly bool $download = false, + public readonly bool $preload = false, + public readonly ?string $importName = null, + public readonly ?string $registryName = null, + ) { + } +} diff --git a/src/Symfony/Component/AssetMapper/LICENSE b/src/Symfony/Component/AssetMapper/LICENSE new file mode 100644 index 0000000000000..3ed9f412ce53d --- /dev/null +++ b/src/Symfony/Component/AssetMapper/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/AssetMapper/MappedAsset.php b/src/Symfony/Component/AssetMapper/MappedAsset.php new file mode 100644 index 0000000000000..7d09bd3acb6f0 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/MappedAsset.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper; + +/** + * Represents a single asset in the asset mapper system. + * + * @experimental + * + * @author Ryan Weaver + */ +final class MappedAsset +{ + public string $publicPath; + /** + * @var string The filesystem path to the source file. + */ + private string $sourcePath; + private string $content; + private string $digest; + private bool $isPredigested; + private ?string $mimeType; + /** @var AssetDependency[] */ + private array $dependencies = []; + + public function __construct(public readonly string $logicalPath) + { + } + + public function getPublicPath(): string + { + return $this->publicPath; + } + + public function getSourcePath(): string + { + return $this->sourcePath; + } + + public function getContent(): string + { + return $this->content; + } + + public function getDigest(): string + { + return $this->digest; + } + + public function isPredigested(): bool + { + return $this->isPredigested; + } + + public function getMimeType(): ?string + { + return $this->mimeType; + } + + public function getExtension(): string + { + return pathinfo($this->logicalPath, \PATHINFO_EXTENSION); + } + + /** + * @return AssetDependency[] + */ + public function getDependencies(): array + { + return $this->dependencies; + } + + public function setPublicPath(string $publicPath): void + { + if (isset($this->publicPath)) { + throw new \LogicException('Cannot set public path: it was already set on the asset.'); + } + + $this->publicPath = $publicPath; + } + + public function setSourcePath(string $sourcePath): void + { + if (isset($this->sourcePath)) { + throw new \LogicException('Cannot set source path: it was already set on the asset.'); + } + + $this->sourcePath = $sourcePath; + } + + public function setDigest(string $digest, bool $isPredigested): void + { + if (isset($this->digest)) { + throw new \LogicException('Cannot set digest: it was already set on the asset.'); + } + + $this->digest = $digest; + $this->isPredigested = $isPredigested; + } + + public function setMimeType(?string $mimeType): void + { + if (isset($this->mimeType)) { + throw new \LogicException('Cannot set mime type: it was already set on the asset.'); + } + + $this->mimeType = $mimeType; + } + + public function setContent(string $content): void + { + if (isset($this->content)) { + throw new \LogicException('Cannot set content: it was already set on the asset.'); + } + + $this->content = $content; + } + + public function addDependency(MappedAsset $asset, bool $isLazy = false): void + { + $this->dependencies[] = new AssetDependency($asset, $isLazy); + } + + public function getPublicPathWithoutDigest(): string + { + if ($this->isPredigested()) { + return $this->getPublicPath(); + } + + // remove last part of publicPath and replace with last part of logicalPath + $publicPathParts = explode('/', $this->getPublicPath()); + $logicalPathParts = explode('/', $this->logicalPath); + array_pop($publicPathParts); + $publicPathParts[] = array_pop($logicalPathParts); + + return implode('/', $publicPathParts); + } +} diff --git a/src/Symfony/Component/AssetMapper/MapperAwareAssetPackage.php b/src/Symfony/Component/AssetMapper/MapperAwareAssetPackage.php new file mode 100644 index 0000000000000..2dfa0b58aaf9f --- /dev/null +++ b/src/Symfony/Component/AssetMapper/MapperAwareAssetPackage.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper; + +use Symfony\Component\Asset\PackageInterface; + +/** + * Decorates asset packages to support resolving assets from the asset mapper. + * + * @experimental + * + * @author Ryan Weaver + */ +final class MapperAwareAssetPackage implements PackageInterface +{ + public function __construct( + private readonly PackageInterface $innerPackage, + private readonly AssetMapperInterface $assetMapper, + ) { + } + + public function getVersion(string $path): string + { + return $this->innerPackage->getVersion($path); + } + + public function getUrl(string $path): string + { + $publicPath = $this->assetMapper->getPublicPath($path); + if ($publicPath) { + $path = ltrim($publicPath, '/'); + } + + return $this->innerPackage->getUrl($path); + } +} diff --git a/src/Symfony/Component/AssetMapper/README.md b/src/Symfony/Component/AssetMapper/README.md new file mode 100644 index 0000000000000..dad6763a644b8 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/README.md @@ -0,0 +1,21 @@ +AssetMapper Component +===================== + +The AssetMapper component allows you to expose directories of assets that are +then moved to a public directory with digested (i.e. versioned) filenames. It +also allows you to dump an [importmap](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) +to allow writing modern JavaScript without a build system. + +**This Component is experimental**. +[Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html) +are not covered by Symfony's +[Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/asset_mapper/introduction.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/AssetMapper/Tests/AssetMapperCompilerTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperCompilerTest.php new file mode 100644 index 0000000000000..8dfdfd1fe04c2 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/AssetMapperCompilerTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AssetMapper\AssetMapperCompiler; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\Compiler\AssetCompilerInterface; +use Symfony\Component\AssetMapper\MappedAsset; + +class AssetMapperCompilerTest extends TestCase +{ + public function testCompile() + { + $compiler1 = new class() implements AssetCompilerInterface { + public function supports(MappedAsset $asset): bool + { + return 'text/css' === $asset->getMimeType(); + } + + public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string + { + return 'should_not_be_called'; + } + }; + + $compiler2 = new class() implements AssetCompilerInterface { + public function supports(MappedAsset $asset): bool + { + return 'application/javascript' === $asset->getMimeType(); + } + + public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string + { + return $content.' compiler2 called'; + } + }; + + $compiler3 = new class() implements AssetCompilerInterface { + public function supports(MappedAsset $asset): bool + { + return 'application/javascript' === $asset->getMimeType(); + } + + public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string + { + return $content.' compiler3 called'; + } + }; + + $compiler = new AssetMapperCompiler([$compiler1, $compiler2, $compiler3]); + $asset = new MappedAsset('foo.js'); + $asset->setMimeType('application/javascript'); + $actualContents = $compiler->compile('starting contents', $asset, $this->createMock(AssetMapperInterface::class)); + $this->assertSame('starting contents compiler2 called compiler3 called', $actualContents); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/AssetMapperDevServerSubscriberFunctionalTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperDevServerSubscriberFunctionalTest.php new file mode 100644 index 0000000000000..aa95be6cce2f6 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/AssetMapperDevServerSubscriberFunctionalTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests; + +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Component\AssetMapper\Tests\fixtures\AssetMapperTestAppKernel; + +class AssetMapperDevServerSubscriberFunctionalTest extends WebTestCase +{ + public function testGettingAssetWorks() + { + $client = static::createClient(); + + $client->request('GET', '/assets/file1-b3445cb7a86a0795a7af7f2004498aef.css'); + $response = $client->getResponse(); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(<<getContent()); + $this->assertSame('"b3445cb7a86a0795a7af7f2004498aef"', $response->headers->get('ETag')); + $this->assertSame('immutable, max-age=604800, public', $response->headers->get('Cache-Control')); + } + + public function test404OnUnknownAsset() + { + $client = static::createClient(); + + $client->request('GET', '/assets/unknown.css'); + $response = $client->getResponse(); + $this->assertSame(404, $response->getStatusCode()); + } + + public function test404OnInvalidDigest() + { + $client = static::createClient(); + + $client->request('GET', '/assets/file1-fakedigest.css'); + $response = $client->getResponse(); + $this->assertSame(404, $response->getStatusCode()); + $this->assertStringContainsString('Asset "file1.css" was found but the digest does not match.', $response->getContent()); + } + + public function testPreDigestedAssetIsReturned() + { + $client = static::createClient(); + + $client->request('GET', '/assets/already-abcdefVWXYZ0123456789.digested.css'); + $response = $client->getResponse(); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(<<getContent()); + } + + protected static function getKernelClass(): string + { + return AssetMapperTestAppKernel::class; + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php new file mode 100644 index 0000000000000..f2110f1e2a2a6 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/AssetMapperRepositoryTest.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AssetMapper\AssetMapperRepository; + +class AssetMapperRepositoryTest extends TestCase +{ + public function testFindWithAbsolutePaths() + { + $repository = new AssetMapperRepository([ + __DIR__.'/fixtures/dir1' => '', + __DIR__.'/fixtures/dir2' => '', + ], __DIR__); + + $this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('file1.css')); + $this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('file4.js')); + $this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('subdir/file5.js')); + $this->assertNull($repository->find('file5.css')); + } + + public function testFindWithRelativePaths() + { + $repository = new AssetMapperRepository([ + 'dir1' => '', + 'dir2' => '', + ], __DIR__.'/fixtures'); + + $this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('file1.css')); + $this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('file4.js')); + $this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('subdir/file5.js')); + $this->assertNull($repository->find('file5.css')); + } + + public function testFindWithNamespaces() + { + $repository = new AssetMapperRepository([ + 'dir1' => 'dir1_namespace', + 'dir2' => 'dir2_namespace', + ], __DIR__.'/fixtures'); + + $this->assertSame(__DIR__.'/fixtures/dir1/file1.css', $repository->find('dir1_namespace/file1.css')); + $this->assertSame(__DIR__.'/fixtures/dir2/file4.js', $repository->find('dir2_namespace/file4.js')); + $this->assertSame(__DIR__.'/fixtures/dir2/subdir/file5.js', $repository->find('dir2_namespace/subdir/file5.js')); + // non-namespaced path does not work + $this->assertNull($repository->find('file4.js')); + } + + public function testFindLogicalPath() + { + $repository = new AssetMapperRepository([ + 'dir1' => '', + 'dir2' => '', + ], __DIR__.'/fixtures'); + $this->assertSame('subdir/file5.js', $repository->findLogicalPath(__DIR__.'/fixtures/dir2/subdir/file5.js')); + } + + public function testAll() + { + $repository = new AssetMapperRepository([ + 'dir1' => '', + 'dir2' => '', + 'dir3' => '', + ], __DIR__.'/fixtures'); + + $actualAllAssets = $repository->all(); + $this->assertCount(8, $actualAllAssets); + + // use realpath to normalize slashes on Windows for comparison + $expectedAllAssets = array_map('realpath', [ + 'file1.css' => __DIR__.'/fixtures/dir1/file1.css', + 'file2.js' => __DIR__.'/fixtures/dir1/file2.js', + 'already-abcdefVWXYZ0123456789.digested.css' => __DIR__.'/fixtures/dir2/already-abcdefVWXYZ0123456789.digested.css', + 'file3.css' => __DIR__.'/fixtures/dir2/file3.css', + 'file4.js' => __DIR__.'/fixtures/dir2/file4.js', + 'subdir'.DIRECTORY_SEPARATOR.'file5.js' => __DIR__.'/fixtures/dir2/subdir/file5.js', + 'subdir'.DIRECTORY_SEPARATOR.'file6.js' => __DIR__.'/fixtures/dir2/subdir/file6.js', + 'test.gif.foo' => __DIR__.'/fixtures/dir3/test.gif.foo', + ]); + $this->assertEquals($expectedAllAssets, array_map('realpath', $actualAllAssets)); + } + + public function testAllWithNamespaces() + { + $repository = new AssetMapperRepository([ + 'dir1' => 'dir1_namespace', + 'dir2' => 'dir2_namespace', + 'dir3' => 'dir3_namespace', + ], __DIR__.'/fixtures'); + + $expectedAllAssets = [ + 'dir1_namespace/file1.css' => __DIR__.'/fixtures/dir1/file1.css', + 'dir1_namespace/file2.js' => __DIR__.'/fixtures/dir1/file2.js', + 'dir2_namespace/already-abcdefVWXYZ0123456789.digested.css' => __DIR__.'/fixtures/dir2/already-abcdefVWXYZ0123456789.digested.css', + 'dir2_namespace/file3.css' => __DIR__.'/fixtures/dir2/file3.css', + 'dir2_namespace/file4.js' => __DIR__.'/fixtures/dir2/file4.js', + 'dir2_namespace/subdir/file5.js' => __DIR__.'/fixtures/dir2/subdir/file5.js', + 'dir2_namespace/subdir/file6.js' => __DIR__.'/fixtures/dir2/subdir/file6.js', + 'dir3_namespace/test.gif.foo' => __DIR__.'/fixtures/dir3/test.gif.foo', + ]; + + $normalizedExpectedAllAssets = []; + foreach ($expectedAllAssets as $key => $val) { + $normalizedExpectedAllAssets[str_replace('/', DIRECTORY_SEPARATOR, $key)] = realpath($val); + } + + $actualAssets = $repository->all(); + $normalizedActualAssets = []; + foreach ($actualAssets as $key => $val) { + $normalizedActualAssets[str_replace('/', DIRECTORY_SEPARATOR, $key)] = realpath($val); + } + + $this->assertEquals($normalizedExpectedAllAssets, $normalizedActualAssets); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php new file mode 100644 index 0000000000000..79cf7267135fd --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperCompiler; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\AssetMapperRepository; +use Symfony\Component\AssetMapper\Compiler\AssetCompilerInterface; +use Symfony\Component\AssetMapper\Compiler\CssAssetUrlCompiler; +use Symfony\Component\AssetMapper\Compiler\JavaScriptImportPathCompiler; +use Symfony\Component\AssetMapper\MappedAsset; + +class AssetMapperTest extends TestCase +{ + public function testGetPublicPrefix() + { + $assetMapper = new AssetMapper( + $this->createMock(AssetMapperRepository::class), + $this->createMock(AssetMapperCompiler::class), + '/projectRootDir/', + '/publicPrefix/', + 'publicDirName', + ); + $this->assertSame('/publicPrefix/', $assetMapper->getPublicPrefix()); + + $assetMapper = new AssetMapper( + $this->createMock(AssetMapperRepository::class), + $this->createMock(AssetMapperCompiler::class), + '/projectRootDir/', + '/publicPrefix', + 'publicDirName', + ); + // The trailing slash should be added automatically + $this->assertSame('/publicPrefix/', $assetMapper->getPublicPrefix()); + } + + public function testGetPublicAssetsFilesystemPath() + { + $assetMapper = new AssetMapper( + $this->createMock(AssetMapperRepository::class), + $this->createMock(AssetMapperCompiler::class), + '/projectRootDir/', + '/publicPrefix/', + 'publicDirName', + ); + $this->assertSame('/projectRootDir/publicDirName/publicPrefix', $assetMapper->getPublicAssetsFilesystemPath()); + } + + public function testGetAsset() + { + $assetMapper = $this->createAssetMapper(); + $this->assertNull($assetMapper->getAsset('non-existent.js')); + + $asset = $assetMapper->getAsset('file2.js'); + $this->assertSame('file2.js', $asset->logicalPath); + $this->assertMatchesRegularExpression('/^\/final-assets\/file2-[a-zA-Z0-9]{7,128}\.js$/', $asset->getPublicPath()); + } + + public function testGetAssetRespectsPreDigestedPaths() + { + $assetMapper = $this->createAssetMapper(); + $asset = $assetMapper->getAsset('already-abcdefVWXYZ0123456789.digested.css'); + $this->assertSame('already-abcdefVWXYZ0123456789.digested.css', $asset->logicalPath); + $this->assertSame('/final-assets/already-abcdefVWXYZ0123456789.digested.css', $asset->getPublicPath()); + } + + public function testGetAssetUsesManifestIfAvailable() + { + $assetMapper = $this->createAssetMapper(); + $asset = $assetMapper->getAsset('file4.js'); + $this->assertSame('/final-assets/file4.checksumfrommanifest.js', $asset->getPublicPath()); + } + + public function testGetPublicPath() + { + $assetMapper = $this->createAssetMapper(); + $this->assertSame('/final-assets/file1-b3445cb7a86a0795a7af7f2004498aef.css', $assetMapper->getPublicPath('file1.css')); + + // check the manifest is used + $this->assertSame('/final-assets/file4.checksumfrommanifest.js', $assetMapper->getPublicPath('file4.js')); + } + + public function testAllAssets() + { + $assetMapper = $this->createAssetMapper(); + $assets = $assetMapper->allAssets(); + $this->assertCount(8, $assets); + $this->assertInstanceOf(MappedAsset::class, $assets[0]); + } + + public function testGetAssetFromFilesystemPath() + { + $assetMapper = $this->createAssetMapper(); + $asset = $assetMapper->getAssetFromSourcePath(__DIR__.'/fixtures/dir1/file1.css'); + $this->assertSame('file1.css', $asset->logicalPath); + } + + public function testGetAssetWithContentBasic() + { + $assetMapper = $this->createAssetMapper(); + $expected = <<getAsset('file1.css'); + $this->assertSame($expected, $asset->getContent()); + + // verify internal caching doesn't cause issues + $asset = $assetMapper->getAsset('file1.css'); + $this->assertSame($expected, $asset->getContent()); + } + + public function testGetAssetWithContentUsesCompilers() + { + $assetMapper = $this->createAssetMapper(); + $expected = <<getAsset('subdir/file5.js'); + $this->assertSame($expected, $asset->getContent()); + } + + public function testGetAssetWithContentErrorsOnCircularReferences() + { + $assetMapper = $this->createAssetMapper('circular_dir'); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Circular reference detected while creating asset for "circular1.css": "circular1.css -> circular2.css -> circular1.css".'); + $assetMapper->getAsset('circular1.css'); + } + + public function testGetAssetWithDigest() + { + $file6Compiler = new class() implements AssetCompilerInterface { + public function supports(MappedAsset $asset): bool + { + return true; + } + + public function compile(string $content, MappedAsset $asset, AssetMapperInterface $assetMapper): string + { + if ('subdir/file6.js' === $asset->logicalPath) { + return $content.'/* compiled */'; + } + + return $content; + } + }; + + $assetMapper = $this->createAssetMapper(); + $asset = $assetMapper->getAsset('subdir/file6.js'); + $this->assertSame('7f983f4053a57f07551fed6099c0da4e', $asset->getDigest()); + $this->assertFalse($asset->isPredigested()); + + // trigger the compiler, which will change file5.js + // since file6.js imports file5.js, the digest for file6 should change, + // because, internally, the file path in file6.js to file5.js will need to change + $assetMapper = $this->createAssetMapper(null, $file6Compiler); + $asset = $assetMapper->getAsset('subdir/file6.js'); + $this->assertSame('7e4f24ebddd4ab2a3bcf0d89270b9f30', $asset->getDigest()); + } + + public function testGetAssetWithPredigested() + { + $assetMapper = $this->createAssetMapper(); + $asset = $assetMapper->getAsset('already-abcdefVWXYZ0123456789.digested.css'); + $this->assertSame('abcdefVWXYZ0123456789.digested', $asset->getDigest()); + $this->assertTrue($asset->isPredigested()); + } + + public function testGetAssetWithMimeType() + { + $assetMapper = $this->createAssetMapper(); + $file1Asset = $assetMapper->getAsset('file1.css'); + $this->assertSame('text/css', $file1Asset->getMimeType()); + $file2Asset = $assetMapper->getAsset('file2.js'); + $this->assertSame('text/javascript', $file2Asset->getMimeType()); + // an extension not in the known extensions + $testAsset = $assetMapper->getAsset('test.gif.foo'); + $this->assertSame('image/gif', $testAsset->getMimeType()); + } + + private function createAssetMapper(string $extraDir = null, AssetCompilerInterface $extraCompiler = null): AssetMapper + { + $dirs = ['dir1' => '', 'dir2' => '', 'dir3' => '']; + if ($extraDir) { + $dirs[$extraDir] = ''; + } + $repository = new AssetMapperRepository($dirs, __DIR__.'/fixtures'); + + $compilers = [ + new JavaScriptImportPathCompiler(), + new CssAssetUrlCompiler(), + ]; + if ($extraCompiler) { + $compilers[] = $extraCompiler; + } + $compiler = new AssetMapperCompiler($compilers); + $extensions = [ + 'foo' => 'image/gif', + ]; + + return new AssetMapper( + $repository, + $compiler, + __DIR__.'/fixtures', + '/final-assets/', + 'test_public', + $extensions, + ); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php new file mode 100644 index 0000000000000..3bbaac82d723e --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/Command/AssetsMapperCompileCommandTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\FrameworkBundle\Tests\Command\AssetsMapperCompileCommand\Fixture\TestAppKernel; +use Symfony\Component\AssetMapper\Tests\fixtures\AssetMapperTestAppKernel; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; + +class AssetsMapperCompileCommandTest extends TestCase +{ + private AssetMapperTestAppKernel $kernel; + private Filesystem $filesystem; + + protected function setUp(): void + { + $this->filesystem = new Filesystem(); + $this->kernel = new AssetMapperTestAppKernel('test', true); + $this->filesystem->mkdir($this->kernel->getProjectDir().'/public'); + } + + protected function tearDown(): void + { + $this->filesystem->remove($this->kernel->getProjectDir().'/public'); + $this->filesystem->remove($this->kernel->getProjectDir().'/var'); + } + + public function testAssetsAreCompiled() + { + $application = new Application($this->kernel); + + $command = $application->find('assetmap:compile'); + $tester = new CommandTester($command); + $res = $tester->execute([]); + $this->assertSame(0, $res); + // match Compiling \d+ assets + $this->assertMatchesRegularExpression('/Compiling \d+ assets/', $tester->getDisplay()); + + $targetBuildDir = $this->kernel->getProjectDir().'/public/assets'; + $this->assertFileExists($targetBuildDir.'/subdir/file5-f4fdc37375c7f5f2629c5659a0579967.js'); + $this->assertSame(<<in($targetBuildDir)->files(); + $this->assertCount(9, $finder); + $this->assertFileExists($targetBuildDir.'/manifest.json'); + $this->assertFileExists($targetBuildDir.'/importmap.json'); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/Compiler/AssetCompilerPathResolverTraitTest.php b/src/Symfony/Component/AssetMapper/Tests/Compiler/AssetCompilerPathResolverTraitTest.php new file mode 100644 index 0000000000000..bb25bdbcd2aef --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/Compiler/AssetCompilerPathResolverTraitTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\Exception\RuntimeException; +use Symfony\Component\AssetMapper\Compiler\AssetCompilerPathResolverTrait; + +class AssetCompilerPathResolverTraitTest extends TestCase +{ + /** + * @dataProvider provideCompileTests + */ + public function testResolvePath(string $directory, string $filename, string $expectedPath) + { + $resolver = new StubTestAssetCompilerPathResolver(); + $this->assertSame($expectedPath, $resolver->doResolvePath($directory, $filename)); + } + + public static function provideCompileTests(): iterable + { + yield 'simple_empty_directory' => [ + 'directory' => '', + 'input' => 'other.js', + 'expectedOutput' => 'other.js', + ]; + + yield 'single_dot' => [ + 'directory' => 'subdir', + 'input' => './other.js', + 'expectedOutput' => 'subdir/other.js', + ]; + + yield 'double_dot' => [ + 'directory' => 'subdir', + 'input' => '../other.js', + 'expectedOutput' => 'other.js', + ]; + + yield 'mixture_of_dots' => [ + 'directory' => 'subdir/another-dir/third-dir', + 'input' => './.././../other.js', + 'expectedOutput' => 'subdir/other.js', + ]; + } + + public function testExceptionIfPathGoesAboveDirectory() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Cannot import the file "../../other.js": it is outside the current "subdir" directory.'); + + $resolver = new StubTestAssetCompilerPathResolver(); + $resolver->doResolvePath('subdir', '../../other.js'); + } +} + +class StubTestAssetCompilerPathResolver +{ + use AssetCompilerPathResolverTrait; + + public function doResolvePath(string $directory, string $filename): string + { + return $this->resolvePath($directory, $filename); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/Compiler/CssAssetUrlCompilerTest.php b/src/Symfony/Component/AssetMapper/Tests/Compiler/CssAssetUrlCompilerTest.php new file mode 100644 index 0000000000000..e4e2ce3a8bdcd --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/Compiler/CssAssetUrlCompilerTest.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AssetMapper\AssetDependency; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\Compiler\CssAssetUrlCompiler; +use Symfony\Component\AssetMapper\MappedAsset; + +class CssAssetUrlCompilerTest extends TestCase +{ + /** + * @dataProvider provideCompileTests + */ + public function testCompile(string $sourceLogicalName, string $input, string $expectedOutput, array $expectedDependencies) + { + $compiler = new CssAssetUrlCompiler(false); + $asset = new MappedAsset($sourceLogicalName); + $this->assertSame($expectedOutput, $compiler->compile($input, $asset, $this->createAssetMapper())); + $assetDependencyLogicalPaths = array_map(fn (AssetDependency $dependency) => $dependency->asset->logicalPath, $asset->getDependencies()); + $this->assertSame($expectedDependencies, $assetDependencyLogicalPaths); + } + + public static function provideCompileTests(): iterable + { + yield 'simple_double_quotes' => [ + 'sourceLogicalName' => 'styles.css', + 'input' => 'body { background: url("images/foo.png"); }', + 'expectedOutput' => 'body { background: url("/assets/images/foo.123456.png"); }', + 'expectedDependencies' => ['images/foo.png'], + ]; + + yield 'simple_multiline' => [ + 'sourceLogicalName' => 'styles.css', + 'input' => << << ['images/foo.png'], + ]; + + yield 'simple_single_quotes' => [ + 'sourceLogicalName' => 'styles.css', + 'input' => 'body { background: url(\'images/foo.png\'); }', + 'expectedOutput' => 'body { background: url("/assets/images/foo.123456.png"); }', + 'expectedDependencies' => ['images/foo.png'], + ]; + + yield 'simple_no_quotes' => [ + 'sourceLogicalName' => 'styles.css', + 'input' => 'body { background: url(images/foo.png); }', + 'expectedOutput' => 'body { background: url("/assets/images/foo.123456.png"); }', + 'expectedDependencies' => ['images/foo.png'], + ]; + + yield 'import_other_css_file' => [ + 'sourceLogicalName' => 'styles.css', + 'input' => '@import url(more-styles.css)', + 'expectedOutput' => '@import url("/assets/more-styles.abcd123.css")', + 'expectedDependencies' => ['more-styles.css'], + ]; + + yield 'move_up_a_directory' => [ + 'sourceLogicalName' => 'styles/app.css', + 'input' => 'body { background: url("../images/foo.png"); }', + 'expectedOutput' => 'body { background: url("/assets/images/foo.123456.png"); }', + 'expectedDependencies' => ['images/foo.png'], + ]; + + yield 'path_not_found_left_alone' => [ + 'sourceLogicalName' => 'styles/app.css', + 'input' => 'body { background: url("../images/bar.png"); }', + 'expectedOutput' => 'body { background: url("../images/bar.png"); }', + 'expectedDependencies' => [], + ]; + + yield 'absolute_paths_left_alone' => [ + 'sourceLogicalName' => 'styles/app.css', + 'input' => 'body { background: url("https://cdn.io/images/bar.png"); }', + 'expectedOutput' => 'body { background: url("https://cdn.io/images/bar.png"); }', + 'expectedDependencies' => [], + ]; + } + + /** + * @dataProvider provideStrictModeTests + */ + public function testStrictMode(string $sourceLogicalName, string $input, ?string $expectedExceptionMessage) + { + if (null !== $expectedExceptionMessage) { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $asset = new MappedAsset($sourceLogicalName); + $asset->setSourcePath('/path/to/styles.css'); + + $compiler = new CssAssetUrlCompiler(true); + $this->assertSame($input, $compiler->compile($input, $asset, $this->createAssetMapper())); + } + + public static function provideStrictModeTests(): iterable + { + yield 'importing_non_existent_file_throws_exception' => [ + 'sourceLogicalName' => 'styles.css', + 'input' => '@import url(non-existent.css)', + 'expectedExceptionMessage' => 'Unable to find asset "non-existent.css" referenced in "/path/to/styles.css".', + ]; + + yield 'importing_absolute_file_path_is_ignored' => [ + 'sourceLogicalName' => 'styles.css', + 'input' => '@import url(/path/to/non-existent.css)', + 'expectedExceptionMessage' => null, + ]; + + yield 'importing_a_url_is_ignored' => [ + 'sourceLogicalName' => 'styles.css', + 'input' => '@import url(https://cdn.io/non-existent.css)', + 'expectedExceptionMessage' => null, + ]; + + yield 'importing_a_data_uri_is_ignored' => [ + 'sourceLogicalName' => 'styles.css', + 'input' => "background-image: url(\'data:image/png;base64,iVBORw0KG\')", + 'expectedExceptionMessage' => null, + ]; + } + + private function createAssetMapper(): AssetMapperInterface + { + $assetMapper = $this->createMock(AssetMapperInterface::class); + $assetMapper->expects($this->any()) + ->method('getAsset') + ->willReturnCallback(function ($path) { + switch ($path) { + case 'images/foo.png': + $asset = new MappedAsset('images/foo.png'); + $asset->setPublicPath('/assets/images/foo.123456.png'); + + return $asset; + case 'more-styles.css': + $asset = new MappedAsset('more-styles.css'); + $asset->setPublicPath('/assets/more-styles.abcd123.css'); + + return $asset; + default: + return null; + } + }); + + return $assetMapper; + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php b/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php new file mode 100644 index 0000000000000..da34a4ef5a8ec --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/Compiler/JavaScriptImportPathCompilerTest.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\Compiler\JavaScriptImportPathCompiler; +use Symfony\Component\AssetMapper\MappedAsset; + +class JavaScriptImportPathCompilerTest extends TestCase +{ + /** + * @dataProvider provideCompileTests + */ + public function testCompile(string $sourceLogicalName, string $input, array $expectedDependencies) + { + $asset = new MappedAsset($sourceLogicalName); + + $compiler = new JavaScriptImportPathCompiler(false); + // compile - and check that content doesn't change + $this->assertSame($input, $compiler->compile($input, $asset, $this->createAssetMapper())); + $actualDependencies = []; + foreach ($asset->getDependencies() as $dependency) { + $actualDependencies[$dependency->asset->logicalPath] = $dependency->isLazy; + } + $this->assertEquals($expectedDependencies, $actualDependencies); + } + + public static function provideCompileTests(): iterable + { + yield 'dynamic_simple_double_quotes' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import("./other.js");', + 'expectedDependencies' => ['other.js' => true] + ]; + + yield 'dynamic_simple_multiline' => [ + 'sourceLogicalName' => 'app.js', + 'input' => << ['other.js' => true] + ]; + + yield 'dynamic_simple_single_quotes' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import(\'./other.js\');', + 'expectedDependencies' => ['other.js' => true] + ]; + + yield 'dynamic_simple_tick_quotes' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import(`./other.js`);', + 'expectedDependencies' => ['other.js' => true] + ]; + + yield 'dynamic_resolves_multiple' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import("./other.js"); import("./subdir/foo.js");', + 'expectedDependencies' => ['other.js' => true, 'subdir/foo.js' => true], + ]; + + yield 'dynamic_avoid_resolving_non_relative_imports' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import("other.js");', + 'expectedDependencies' => [], + ]; + + yield 'dynamic_resolves_dynamic_imports_later_in_file' => [ + 'sourceLogicalName' => 'app.js', + 'input' => "console.log('Hello test!');\n import('./subdir/foo.js').then(() => console.log('inside promise!'));", + 'expectedDependencies' => ['subdir/foo.js' => true], + ]; + + yield 'dynamic_correctly_moves_to_higher_directories' => [ + 'sourceLogicalName' => 'subdir/app.js', + 'input' => 'import("../other.js");', + 'expectedDependencies' => ['other.js' => true], + ]; + + yield 'static_named_import_double_quotes' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import { myFunction } from "./other.js";', + 'expectedDependencies' => ['other.js' => false], + ]; + + yield 'static_named_import_single_quotes' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import { myFunction } from \'./other.js\';', + 'expectedDependencies' => ['other.js' => false], + ]; + + yield 'static_default_import' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import myFunction from "./other.js";', + 'expectedDependencies' => ['other.js' => false], + ]; + + yield 'static_default_and_named_import' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import myFunction, { helperFunction } from "./other.js";', + 'expectedDependencies' => ['other.js' => false], + ]; + + yield 'static_import_everything' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import * as myModule from "./other.js";', + 'expectedDependencies' => ['other.js' => false], + ]; + + yield 'static_import_just_for_side_effects' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import "./other.js";', + 'expectedDependencies' => ['other.js' => false], + ]; + + yield 'mix_of_static_and_dynamic_imports' => [ + 'sourceLogicalName' => 'app.js', + 'input' => 'import "./other.js"; import("./subdir/foo.js");', + 'expectedDependencies' => ['other.js' => false, 'subdir/foo.js' => true], + ]; + + yield 'extra_import_word_does_not_cause_issues' => [ + 'sourceLogicalName' => 'app.js', + 'input' => "// about to do an import\nimport('./other.js');", + 'expectedDependencies' => ['other.js' => true], + ]; + + yield 'import_on_one_line_then_module_name_on_next_is_ok' => [ + 'sourceLogicalName' => 'app.js', + 'input' => "import \n './other.js';", + 'expectedDependencies' => ['other.js' => false], + ]; + + yield 'importing_a_css_file_is_not_included' => [ + 'sourceLogicalName' => 'app.js', + 'input' => "import './styles.css';", + 'expectedDependencies' => [], + ]; + + yield 'importing_non_existent_file_without_strict_mode_is_ignored' => [ + 'sourceLogicalName' => 'app.js', + 'input' => "import './non-existent.js';", + 'expectedDependencies' => [], + ]; + } + + /** + * @dataProvider provideStrictModeTests + */ + public function testStrictMode(string $sourceLogicalName, string $input, ?string $expectedExceptionMessage) + { + if (null !== $expectedExceptionMessage) { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + } + + $asset = new MappedAsset($sourceLogicalName); + $asset->setSourcePath('/path/to/app.js'); + + $compiler = new JavaScriptImportPathCompiler(true); + $this->assertSame($input, $compiler->compile($input, $asset, $this->createAssetMapper())); + } + + public static function provideStrictModeTests(): iterable + { + yield 'importing_non_existent_file_throws_exception' => [ + 'sourceLogicalName' => 'app.js', + 'input' => "import './non-existent.js';", + 'expectedExceptionMessage' => 'Unable to find asset "non-existent.js" imported from "/path/to/app.js".', + ]; + + yield 'importing_file_just_missing_js_extension_adds_extra_info' => [ + 'sourceLogicalName' => 'app.js', + 'input' => "import './other';", + 'expectedExceptionMessage' => 'Unable to find asset "other" imported from "/path/to/app.js". Try adding ".js" to the end of the import - i.e. "other.js".', + ]; + + yield 'importing_absolute_file_path_is_ignored' => [ + 'sourceLogicalName' => 'app.js', + 'input' => "import '/path/to/other.js';", + 'expectedExceptionMessage' => null, + ]; + + yield 'importing_a_url_is_ignored' => [ + 'sourceLogicalName' => 'app.js', + 'input' => "import 'https://example.com/other.js';", + 'expectedExceptionMessage' => null, + ]; + } + + private function createAssetMapper(): AssetMapperInterface + { + $assetMapper = $this->createMock(AssetMapperInterface::class); + $assetMapper->expects($this->any()) + ->method('getAsset') + ->willReturnCallback(function ($path) { + switch ($path) { + case 'other.js': + $asset = new MappedAsset('other.js'); + $asset->setMimeType('application/javascript'); + + return $asset; + case 'subdir/foo.js': + $asset = new MappedAsset('subdir/foo.js'); + $asset->setMimeType('text/javascript'); + + return $asset; + case 'dir_with_index/index.js': + $asset = new MappedAsset('dir_with_index/index.js'); + $asset->setMimeType('text/javascript'); + + return $asset; + case 'styles.css': + $asset = new MappedAsset('styles.css'); + $asset->setMimeType('text/css'); + + return $asset; + default: + return null; + } + }); + + return $assetMapper; + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/Compiler/SourceMappingUrlsCompilerTest.php b/src/Symfony/Component/AssetMapper/Tests/Compiler/SourceMappingUrlsCompilerTest.php new file mode 100644 index 0000000000000..838b3d90d97d0 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/Compiler/SourceMappingUrlsCompilerTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AssetMapper\AssetDependency; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\Compiler\SourceMappingUrlsCompiler; +use Symfony\Component\AssetMapper\MappedAsset; + +class SourceMappingUrlsCompilerTest extends TestCase +{ + /** + * @dataProvider provideCompileTests + */ + public function testCompile(string $sourceLogicalName, string $input, string $expectedOutput, $expectedDependencies) + { + $assetMapper = $this->createMock(AssetMapperInterface::class); + $assetMapper->expects($this->any()) + ->method('getAsset') + ->willReturnCallback(function ($path) { + switch ($path) { + case 'foo.js.map': + $asset = new MappedAsset('foo.js.map'); + $asset->setPublicPath('/assets/foo.123456.js.map'); + + return $asset; + case 'styles/bar.css.map': + $asset = new MappedAsset('styles/bar.css.map'); + $asset->setPublicPath('/assets/styles/bar.abcd123.css.map'); + + return $asset; + default: + return null; + } + }); + + $compiler = new SourceMappingUrlsCompiler(); + $asset = new MappedAsset($sourceLogicalName); + $this->assertSame($expectedOutput, $compiler->compile($input, $asset, $assetMapper)); + $assetDependencyLogicalPaths = array_map(fn (AssetDependency $dependency) => $dependency->asset->logicalPath, $asset->getDependencies()); + $this->assertSame($expectedDependencies, $assetDependencyLogicalPaths); + } + + public static function provideCompileTests(): iterable + { + yield 'js_simple_sourcemap' => [ + 'sourceLogicalName' => 'foo.js', + 'input' => << << ['foo.js.map'], + ]; + + yield 'css_simple_sourcemap' => [ + 'sourceLogicalName' => 'styles/bar.css', + 'input' => << << ['styles/bar.css.map'], + ]; + + yield 'no_sourcemap_found' => [ + 'sourceLogicalName' => 'styles/bar.css', + 'input' => << << [], + ]; + + yield 'path_not_in_asset_mapper_is_left_alone' => [ + 'sourceLogicalName' => 'styles/bar.css', + 'input' => << << [], + ]; + + yield 'sourcemap_outside_of_comment_left_alone' => [ + 'sourceLogicalName' => 'styles/bar.css', + 'input' => << << [], + ]; + + yield 'sourcemap_not_at_start_of_line_left_alone' => [ + 'sourceLogicalName' => 'styles/bar.css', + 'input' => << << [], + ]; + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php new file mode 100644 index 0000000000000..4c75084c8681d --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapManagerTest.php @@ -0,0 +1,457 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests\ImportMap; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperCompiler; +use Symfony\Component\AssetMapper\AssetMapperRepository; +use Symfony\Component\AssetMapper\Compiler\JavaScriptImportPathCompiler; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\AssetMapper\ImportMap\PackageRequireOptions; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; + +class ImportMapManagerTest extends TestCase +{ + private MockHttpClient $httpClient; + private Filesystem $filesystem; + + + protected function setUp(): void + { + $this->filesystem = new Filesystem(); + if (!file_exists(__DIR__ . '/../fixtures/importmaps_for_writing')) { + $this->filesystem->mkdir(__DIR__ . '/../fixtures/importmaps_for_writing'); + } + } + + protected function tearDown(): void + { + $this->filesystem->remove(__DIR__ . '/../fixtures/importmaps_for_writing'); + } + + public function testGetModulesToPreload() + { + $manager = $this->createImportMapManager( + ['assets' => '', 'assets2' => 'namespaced_assets2'], + __DIR__ . '/../fixtures/importmaps/' + ); + $this->assertEquals([ + 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js', + '/assets/app-ea9ebe6156adc038aba53164e2be0867.js', + // these are non-lazily imported from app.js + '/assets/pizza/index-b3fb5ee31adaf5e1b32d28edf1ab8e7a.js', + '/assets/popcorn-c0778b84ef9893592385aebc95a2896e.js', + ], $manager->getModulesToPreload()); + } + + public function testGetImportMapJson() + { + $manager = $this->createImportMapManager( + ['assets' => '', 'assets2' => 'namespaced_assets2'], + __DIR__ . '/../fixtures/importmaps/' + ); + $this->assertEquals(['imports' => [ + '@hotwired/stimulus' => 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js', + 'lodash' => '/assets/vendor/lodash-ad7bd7bf42edd09654255a82b9027810.js', + 'app' => '/assets/app-ea9ebe6156adc038aba53164e2be0867.js', + '/assets/pizza/index.js' => '/assets/pizza/index-b3fb5ee31adaf5e1b32d28edf1ab8e7a.js', + '/assets/popcorn.js' => '/assets/popcorn-c0778b84ef9893592385aebc95a2896e.js', + '/assets/imported_async.js' => '/assets/imported_async-8f0cd418bfeb0cf63826e09a4474a81c.js', + 'other_app' => '/assets/namespaced_assets2/app2-344d0d513d424647e7d8a394ffe5e4b5.js', + ]], json_decode($manager->getImportMapJson(), true)); + } + + public function testGetImportMapJsonUsesDumpedFile() + { + $manager = $this->createImportMapManager( + ['assets' => ''], + __DIR__ . '/../fixtures/', + '/final-assets', + 'test_public' + ); + $this->assertEquals(['imports' => [ + '@hotwired/stimulus' => 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js', + 'app' => '/assets/app-ea9ebe6156adc038aba53164e2be0867.js', + ]], json_decode($manager->getImportMapJson(), true)); + } + + /** + * @dataProvider getRequirePackageTests + */ + public function testRequire(array $packages, array $expectedInstallRequest, array $responseMap, array $expectedImportMap, array $expectedDownloadedFiles) + { + $rootDir = __DIR__ . '/../fixtures/importmaps_for_writing'; + $manager = $this->createImportMapManager(['assets' => ''], $rootDir); + + $expectedRequestBody = [ + 'install' => $expectedInstallRequest, + 'flattenScope' => true, + 'env' => ['browser', 'module', 'production'], + ]; + $responseData = [ + 'map' => [ + 'imports' => $responseMap, + ], + ]; + $responses = []; + $responses[] = function ($method, $url, $options) use ($responseData, $expectedRequestBody) { + $this->assertSame('POST', $method); + $this->assertSame('https://example.com/generate', $url); + $this->assertSame($expectedRequestBody, json_decode($options['body'], true)); + + return new MockResponse(json_encode($responseData)); + }; + // mock the "file download" requests + foreach ($expectedDownloadedFiles as $file) { + $responses[] = new MockResponse(sprintf('contents of %s', $file)); + } + $this->httpClient->setResponseFactory($responses); + + $manager->require($packages); + $actualImportMap = require($rootDir.'/importmap.php'); + $this->assertEquals($expectedImportMap, $actualImportMap); + foreach ($expectedDownloadedFiles as $file) { + $this->assertFileExists($rootDir.'/' . $file); + $actualContents = file_get_contents($rootDir.'/' . $file); + $this->assertSame(sprintf('contents of %s', $file), $actualContents); + } + } + + public static function getRequirePackageTests(): iterable + { + yield 'require single lodash package' => [ + 'packages' => [new PackageRequireOptions('lodash')], + 'expectedInstallRequest' => ['lodash'], + 'responseMap' => [ + 'lodash' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + 'expectedImportMap' => [ + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ] + ], + 'expectedDownloadedFiles' => [], + ]; + + yield 'require two packages' => [ + 'packages' => [new PackageRequireOptions('lodash'), new PackageRequireOptions('cowsay')], + 'expectedInstallRequest' => ['lodash', 'cowsay'], + 'responseMap' => [ + 'lodash' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + 'cowsay' => 'https://ga.jspm.io/npm:cowsay@4.5.6/cowsay.js', + ], + 'expectedImportMap' => [ + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + 'cowsay' => [ + 'url' => 'https://ga.jspm.io/npm:cowsay@4.5.6/cowsay.js', + ], + ], + 'expectedDownloadedFiles' => [], + ]; + + yield 'single_package_that_returns_as_two' => [ + 'packages' => [new PackageRequireOptions('lodash')], + 'expectedInstallRequest' => ['lodash'], + 'responseMap' => [ + 'lodash' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + 'lodash-dependency' => 'https://ga.jspm.io/npm:lodash-dependency@9.8.7/lodash-dependency.js', + ], + 'expectedImportMap' => [ + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + 'lodash-dependency' => [ + 'url' => 'https://ga.jspm.io/npm:lodash-dependency@9.8.7/lodash-dependency.js', + ], + ], + 'expectedDownloadedFiles' => [], + ]; + + yield 'single_package_with_version_constraint' => [ + 'packages' => [new PackageRequireOptions('lodash', '^1.2.3')], + 'expectedInstallRequest' => ['lodash@^1.2.3'], + 'responseMap' => [ + 'lodash' => 'https://ga.jspm.io/npm:lodash@1.2.7/lodash.js', + ], + 'expectedImportMap' => [ + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.7/lodash.js', + ], + ], + 'expectedDownloadedFiles' => [], + ]; + + yield 'single_package_that_downloads' => [ + 'packages' => [new PackageRequireOptions('lodash', download: true)], + 'expectedInstallRequest' => ['lodash'], + 'responseMap' => [ + 'lodash' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + 'expectedImportMap' => [ + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + 'downloaded_to' => 'vendor/lodash.js', + ], + ], + 'expectedDownloadedFiles' => [ + 'assets/vendor/lodash.js', + ], + ]; + + yield 'single_package_that_preloads' => [ + 'packages' => [new PackageRequireOptions('lodash', preload: true)], + 'expectedInstallRequest' => ['lodash'], + 'responseMap' => [ + 'lodash' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + 'expectedImportMap' => [ + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + 'preload' => true, + ], + ], + 'expectedDownloadedFiles' => [], + ]; + + yield 'single_package_with_custom_import_name' => [ + 'packages' => [new PackageRequireOptions('lodash', importName: 'lodash-es')], + 'expectedInstallRequest' => ['lodash'], + 'responseMap' => [ + 'lodash' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + 'expectedImportMap' => [ + 'lodash-es' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + ], + 'expectedDownloadedFiles' => [], + ]; + + yield 'single_package_with_jspm_custom_registry' => [ + 'packages' => [new PackageRequireOptions('lodash', registryName: 'jspm')], + 'expectedInstallRequest' => ['jspm:lodash'], + 'responseMap' => [ + 'lodash' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + 'expectedImportMap' => [ + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + ], + 'expectedDownloadedFiles' => [], + ]; + } + + public function testRemove() + { + $rootDir = __DIR__ . '/../fixtures/importmaps_for_writing'; + $manager = $this->createImportMapManager(['assets' => ''], $rootDir); + + $map = [ + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + 'cowsay' => [ + 'url' => 'https://ga.jspm.io/npm:cowsay@4.5.6/cowsay.umd.js', + 'downloaded_to' => 'vendor/moo.js', + ], + 'chance' => [ + 'url' => 'https://ga.jspm.io/npm:chance@7.8.9/build/chance.js', + 'downloaded_to' => 'vendor/chance.js', + ], + 'app' => [ + 'path' => 'app.js', + ], + 'other' => [ + 'path' => 'other.js', + ], + ]; + $mapString = var_export($map, true); + file_put_contents($rootDir.'/importmap.php', "filesystem->mkdir($rootDir.'/assets/vendor'); + touch($rootDir.'/assets/vendor/moo.js'); + touch($rootDir.'/assets/vendor/chance.js'); + touch($rootDir.'/assets/app.js'); + touch($rootDir.'/assets/other.js'); + + $manager->remove(['cowsay', 'app']); + $actualImportMap = require($rootDir.'/importmap.php'); + $expectedImportMap = $map; + unset($expectedImportMap['cowsay'], $expectedImportMap['app']); + $this->assertEquals($expectedImportMap, $actualImportMap); + $this->assertFileDoesNotExist($rootDir.'/assets/vendor/moo.js'); + $this->assertFileDoesNotExist($rootDir.'/assets/app.js'); + $this->assertFileExists($rootDir.'/assets/vendor/chance.js'); + $this->assertFileExists($rootDir.'/assets/other.js'); + } + + public function testUpdate() + { + $rootDir = __DIR__ . '/../fixtures/importmaps_for_writing'; + $manager = $this->createImportMapManager(['assets' => ''], $rootDir); + + $map = [ + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.3/lodash.js', + ], + 'cowsay' => [ + 'url' => 'https://ga.jspm.io/npm:cowsay@4.5.6/cowsay.umd.js', + 'downloaded_to' => 'vendor/moo.js', + ], + 'canvas-confetti' => [ + 'url' => 'https://cdn.skypack.dev/pin/canvas-confetti@v1.5.0-t438JJTXIbBReqvLtDua/mode=imports,min/optimized/canvas-confetti.js', + 'preload' => true, + ], + 'app' => [ + 'path' => 'app.js', + ], + ]; + $mapString = var_export($map, true); + file_put_contents($rootDir.'/importmap.php', "filesystem->mkdir($rootDir.'/assets/vendor'); + file_put_contents($rootDir.'/assets/vendor/moo.js', 'moo.js contents'); + file_put_contents($rootDir.'/assets/app.js', 'app.js contents'); + + $responses = []; + $responses[] = function ($method, $url, $options) { + $this->assertSame('POST', $method); + $this->assertSame('https://example.com/generate', $url); + + return new MockResponse(json_encode([ + 'map' => [ + 'imports' => [ + 'lodash' => 'https://ga.jspm.io/npm:lodash@1.2.9/lodash.js', + 'cowsay' => 'https://ga.jspm.io/npm:cowsay@4.5.9/cowsay.umd.js', + 'canvas-confetti' => 'https://cdn.skypack.dev/pin/canvas-confetti@v1.6.0-t438JJTXIbBReqvLtDua/mode=imports,min/optimized/canvas-confetti.js', + ], + ], + ])); + }; + // 1 file will be downloaded + $responses[] = new MockResponse(sprintf('contents of cowsay.js')); + $this->httpClient->setResponseFactory($responses); + + $manager->update(); + $actualImportMap = require($rootDir.'/importmap.php'); + $expectedImportMap = [ + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@1.2.9/lodash.js', + ], + 'cowsay' => [ + 'url' => 'https://ga.jspm.io/npm:cowsay@4.5.9/cowsay.umd.js', + 'downloaded_to' => 'vendor/cowsay.js', + ], + // a non-jspm URL so we can make sure it updates + 'canvas-confetti' => [ + 'url' => 'https://cdn.skypack.dev/pin/canvas-confetti@v1.6.0-t438JJTXIbBReqvLtDua/mode=imports,min/optimized/canvas-confetti.js', + 'preload' => true, + ], + 'app' => [ + 'path' => 'app.js', + ], + ]; + $this->assertEquals($expectedImportMap, $actualImportMap); + $this->assertFileDoesNotExist($rootDir.'/assets/vendor/moo.js'); + $this->assertFileExists($rootDir.'/assets/vendor/cowsay.js'); + $actualContents = file_get_contents($rootDir.'/assets/vendor/cowsay.js'); + $this->assertSame('contents of cowsay.js', $actualContents); + } + + /** + * @dataProvider getPackageNameTests + */ + public function testParsePackageName(string $packageName, array $expectedReturn) + { + $parsed = ImportMapManager::parsePackageName($packageName); + // remove integer keys - they're noise + + if (is_array($parsed)) { + $parsed = array_filter($parsed, function ($key) { + return !is_int($key); + }, ARRAY_FILTER_USE_KEY); + } + $this->assertEquals($expectedReturn, $parsed); + } + + public static function getPackageNameTests(): iterable + { + yield 'simple' => [ + 'lodash', + [ + 'package' => 'lodash', + 'registry' => '', + ], + ]; + + yield 'with_version_constraint' => [ + 'lodash@^1.2.3', + [ + 'package' => 'lodash', + 'registry' => '', + 'version' => '^1.2.3', + ], + ]; + + yield 'with_registry' => [ + 'npm:lodash', + [ + 'package' => 'lodash', + 'registry' => 'npm', + ], + ]; + + yield 'with_registry_and_version' => [ + 'npm:lodash@^1.2.3', + [ + 'package' => 'lodash', + 'registry' => 'npm', + 'version' => '^1.2.3', + ], + ]; + } + + private function createImportMapManager(array $dirs, string $rootDir, string $publicPrefix = '/assets/', string $publicDirName = 'public'): ImportMapManager + { + $mapper = $this->createAssetMapper($dirs, $rootDir, $publicPrefix, $publicDirName); + $this->httpClient = new MockHttpClient(); + + return new ImportMapManager( + $mapper, + $rootDir . '/importmap.php', + $rootDir . '/assets/vendor', + ImportMapManager::PROVIDER_JSPM, + $this->httpClient + ); + } + + private function createAssetMapper(array $dirs, string $rootDir, string $publicPrefix = '/assets/', string $publicDirName = 'public'): AssetMapper + { + $repository = new AssetMapperRepository($dirs, $rootDir); + + $compiler = new AssetMapperCompiler([ + new JavaScriptImportPathCompiler(), + ]); + + return new AssetMapper( + $repository, + $compiler, + $rootDir, + $publicPrefix, + $publicDirName, + ); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapRendererTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapRendererTest.php new file mode 100644 index 0000000000000..7742e9e7841ab --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapRendererTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests\ImportMap; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; + +class ImportMapRendererTest extends TestCase +{ + public function testBasicRenderNoEntry() + { + $renderer = new ImportMapRenderer($this->createImportMapManager()); + $html = $renderer->render(); + $this->assertStringContainsString(<< + {"imports":{}} + + EOF + , $html); + $this->assertStringContainsString('', $html); + } + + public function testWithEntrypoint() + { + $renderer = new ImportMapRenderer($this->createImportMapManager()); + $this->assertStringContainsString("", $renderer->render('application')); + + $renderer = new ImportMapRenderer($this->createImportMapManager()); + $this->assertStringContainsString("", $renderer->render("application's")); + } + + public function testWithPreloads() + { + $renderer = new ImportMapRenderer($this->createImportMapManager([ + '/assets/application.js', + 'https://cdn.example.com/assets/foo.js' + ])); + $html = $renderer->render(); + $this->assertStringContainsString('', $html); + $this->assertStringContainsString('', $html); + } + + private function createImportMapManager(array $urlsToPreload = []): ImportMapManager + { + $importMapManager = $this->createMock(ImportMapManager::class); + $importMapManager->expects($this->once()) + ->method('getImportMapJson') + ->willReturn('{"imports":{}}'); + + $importMapManager->expects($this->once()) + ->method('getModulesToPreload') + ->willReturn($urlsToPreload); + + return $importMapManager; + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/MappedAssetTest.php b/src/Symfony/Component/AssetMapper/Tests/MappedAssetTest.php new file mode 100644 index 0000000000000..244c70dd2ad69 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/MappedAssetTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AssetMapper\MappedAsset; + +class MappedAssetTest extends TestCase +{ + public function testGetLogicalPath() + { + $asset = new MappedAsset('foo.css'); + + $this->assertSame('foo.css', $asset->logicalPath); + } + + public function testGetPublicPath() + { + $asset = new MappedAsset('anything'); + $asset->setPublicPath('/assets/foo.1234567.css'); + + $this->assertSame('/assets/foo.1234567.css', $asset->getPublicPath()); + } + + /** + * @dataProvider getExtensionTests + */ + public function testGetExtension(string $filename, string $expectedExtension) + { + $asset = new MappedAsset($filename); + + $this->assertSame($expectedExtension, $asset->getExtension()); + } + + public static function getExtensionTests(): iterable + { + yield 'simple' => ['foo.css', 'css']; + yield 'with_multiple_dot' => ['foo.css.map', 'map']; + yield 'with_directory' => ['foo/bar.css', 'css']; + } + + public function testGetSourcePath(): void + { + $asset = new MappedAsset('foo.css'); + $asset->setSourcePath('/path/to/source.css'); + $this->assertSame('/path/to/source.css', $asset->getSourcePath()); + } + + public function testGetMimeType(): void + { + $asset = new MappedAsset('foo.css'); + $asset->setMimeType('text/css'); + $this->assertSame('text/css', $asset->getMimeType()); + } + + public function testGetDigest(): void + { + $asset = new MappedAsset('foo.css'); + $asset->setDigest('1234567', false); + $this->assertSame('1234567', $asset->getDigest()); + $this->assertFalse($asset->isPredigested()); + } + + public function testGetContent(): void + { + $asset = new MappedAsset('foo.css'); + $asset->setContent('body { color: red; }'); + $this->assertSame('body { color: red; }', $asset->getContent()); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/MapperAwareAssetPackageIntegrationTest.php b/src/Symfony/Component/AssetMapper/Tests/MapperAwareAssetPackageIntegrationTest.php new file mode 100644 index 0000000000000..1f6c627df3aec --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/MapperAwareAssetPackageIntegrationTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Asset\Packages; +use Symfony\Component\AssetMapper\Tests\fixtures\AssetMapperTestAppKernel; + +class MapperAwareAssetPackageIntegrationTest extends KernelTestCase +{ + public function testDefaultAssetPackageIsDecorated() + { + $kernel = new AssetMapperTestAppKernel('test', true); + $kernel->boot(); + + $packages = $kernel->getContainer()->get('public.assets.packages'); + \assert($packages instanceof Packages); + $this->assertSame('/assets/file1-b3445cb7a86a0795a7af7f2004498aef.css', $packages->getUrl('file1.css')); + $this->assertSame('/non-existent.css', $packages->getUrl('non-existent.css')); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/MapperAwareAssetPackageTest.php b/src/Symfony/Component/AssetMapper/Tests/MapperAwareAssetPackageTest.php new file mode 100644 index 0000000000000..fd97904af3b30 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/MapperAwareAssetPackageTest.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\PackageInterface; +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\MapperAwareAssetPackage; + +class MapperAwareAssetPackageTest extends TestCase +{ + public function testGetVersion() + { + $inner = $this->createMock(PackageInterface::class); + $inner->expects($this->once()) + ->method('getVersion') + ->with('foo') + ->willReturn('2.0'); + + $assetMapperPackage = new MapperAwareAssetPackage($inner, $this->createMock(AssetMapperInterface::class)); + + $this->assertSame('2.0', $assetMapperPackage->getVersion('foo')); + } + + /** + * @dataProvider getUrlTests + */ + public function testGetUrl(string $path, string $expectedPathSentToInner) + { + $inner = $this->createMock(PackageInterface::class); + $inner->expects($this->once()) + ->method('getUrl') + ->with($expectedPathSentToInner) + ->willReturnCallback(function ($path) { + return '/'.$path; + }); + $assetMapper = $this->createMock(AssetMapperInterface::class); + $assetMapper->expects($this->any()) + ->method('getPublicPath') + ->willReturnCallback(function ($path) { + switch ($path) { + case 'images/foo.png': + return '/assets/images/foo.123456.png'; + case 'more-styles.css': + return '/assets/more-styles.abcd123.css'; + default: + return null; + } + }); + + $assetMapperPackage = new MapperAwareAssetPackage($inner, $assetMapper); + $this->assertSame('/'.$expectedPathSentToInner, $assetMapperPackage->getUrl($path)); + } + + public static function getUrlTests(): iterable + { + yield 'path_is_found_in_asset_mapper' => [ + 'path' => 'images/foo.png', + 'expectedPathSentToInner' => 'assets/images/foo.123456.png', + ]; + + yield 'path_not_found_in_asset_mapper' => [ + 'path' => 'styles.css', + 'expectedPathSentToInner' => 'styles.css', + ]; + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/AssetMapperTestAppKernel.php b/src/Symfony/Component/AssetMapper/Tests/fixtures/AssetMapperTestAppKernel.php new file mode 100644 index 0000000000000..8225efdfb24fd --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/AssetMapperTestAppKernel.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AssetMapper\Tests\fixtures; + +use Psr\Log\NullLogger; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Kernel; + +class AssetMapperTestAppKernel extends Kernel +{ + public function registerBundles(): iterable + { + return [ + new FrameworkBundle(), + ]; + } + + public function getProjectDir(): string + { + return __DIR__; + } + + public function registerContainerConfiguration(LoaderInterface $loader): void + { + $loader->load(static function (ContainerBuilder $container) { + $container->loadFromExtension('framework', [ + 'http_method_override' => false, + 'assets' => null, + 'asset_mapper' => [ + 'paths' => ['dir1', 'dir2'], + ], + 'test' => true, + ]); + + $container->setAlias('public.assets.packages', new Alias('assets.packages', true)); + }); + } + + protected function build(ContainerBuilder $container): void + { + $container->register('logger', NullLogger::class); + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/circular_dir/circular1.css b/src/Symfony/Component/AssetMapper/Tests/fixtures/circular_dir/circular1.css new file mode 100644 index 0000000000000..841628d41979e --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/circular_dir/circular1.css @@ -0,0 +1 @@ +@import url(circular2.css); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/circular_dir/circular2.css b/src/Symfony/Component/AssetMapper/Tests/fixtures/circular_dir/circular2.css new file mode 100644 index 0000000000000..26e6498940443 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/circular_dir/circular2.css @@ -0,0 +1 @@ +@import url(circular1.css); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/dir1/file1.css b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir1/file1.css new file mode 100644 index 0000000000000..cc4c83cdd389e --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir1/file1.css @@ -0,0 +1,2 @@ +/* file1.css */ +body {} diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/dir1/file2.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir1/file2.js new file mode 100644 index 0000000000000..cba61d3118d2c --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir1/file2.js @@ -0,0 +1 @@ +console.log('file2.js'); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/already-abcdefVWXYZ0123456789.digested.css b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/already-abcdefVWXYZ0123456789.digested.css new file mode 100644 index 0000000000000..8ee3e02fb0086 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/already-abcdefVWXYZ0123456789.digested.css @@ -0,0 +1,2 @@ +/* already-abcdefVWXYZ0123456789.digested.css */ +body {} diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/file3.css b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/file3.css new file mode 100644 index 0000000000000..493a16dd6757e --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/file3.css @@ -0,0 +1,2 @@ +/* file3.css */ +body {} diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/file4.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/file4.js new file mode 100644 index 0000000000000..3d8e8aa0770dd --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/file4.js @@ -0,0 +1 @@ +console.log('file4.js'); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/subdir/file5.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/subdir/file5.js new file mode 100644 index 0000000000000..50ae355aba6c8 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/subdir/file5.js @@ -0,0 +1,2 @@ +import '../file4.js'; +console.log('file5.js'); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/subdir/file6.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/subdir/file6.js new file mode 100644 index 0000000000000..602ae173f3d40 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir2/subdir/file6.js @@ -0,0 +1,2 @@ +import './file5.js'; +console.log('file6.js'); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/dir3/test.gif.foo b/src/Symfony/Component/AssetMapper/Tests/fixtures/dir3/test.gif.foo new file mode 100644 index 0000000000000000000000000000000000000000..6b44fc7dfb859cbcfd1b5cc72b1fa68ddd5b4328 GIT binary patch literal 801 wcmZ?wbhEHbWMW`q_|Cxa|Nno6Q7{?;BQ*qcKpqF>1qKc_21X7R0RaYU0BZ&YM*si- literal 0 HcmV?d00001 diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/app.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/app.js new file mode 100644 index 0000000000000..5c0f7a1fb2a16 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/app.js @@ -0,0 +1,7 @@ +import + './pizza/index.js'; +import Popcorn from './popcorn.js'; + +import('./imported_async.js').then(() => { + console.log('async import done'); +}); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/imported_async.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/imported_async.js new file mode 100644 index 0000000000000..47641c0e7e50c --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/imported_async.js @@ -0,0 +1 @@ +console.log('imported_async.js'); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/pizza/index.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/pizza/index.js new file mode 100644 index 0000000000000..dc37421341e49 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/pizza/index.js @@ -0,0 +1 @@ +console.log('pizza/index.js'); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/popcorn.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/popcorn.js new file mode 100644 index 0000000000000..06a3046c979ad --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/popcorn.js @@ -0,0 +1 @@ +console.log('popcorn.js'); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/vendor/lodash.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/vendor/lodash.js new file mode 100644 index 0000000000000..ac1d7f73afb58 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets/vendor/lodash.js @@ -0,0 +1 @@ +console.log('fake downloaded lodash.js'); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets2/app2.js b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets2/app2.js new file mode 100644 index 0000000000000..b565c50b03ce9 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/assets2/app2.js @@ -0,0 +1 @@ +console.log('app2'); diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/importmap.php b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/importmap.php new file mode 100644 index 0000000000000..8071f7ecb4c18 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/importmaps/importmap.php @@ -0,0 +1,20 @@ + [ + 'url' => 'https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js', + 'preload' => true, + ], + 'lodash' => [ + 'url' => 'https://ga.jspm.io/npm:lodash@4.17.21/lodash.js', + 'downloaded_to' => 'vendor/lodash.js', + ], + 'app' => [ + 'path' => 'app.js', + 'preload' => true, + ], + 'other_app' => [ + // "namespaced_assets2" is defined as a namespaced path in the test + 'path' => 'namespaced_assets2/app2.js', + ] +]; diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/test_public/final-assets/importmap.json b/src/Symfony/Component/AssetMapper/Tests/fixtures/test_public/final-assets/importmap.json new file mode 100644 index 0000000000000..20b6c40cf9679 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/test_public/final-assets/importmap.json @@ -0,0 +1,6 @@ +{ + "imports": { + "@hotwired/stimulus": "https://unpkg.com/@hotwired/stimulus@3.2.1/dist/stimulus.js", + "app": "/assets/app-ea9ebe6156adc038aba53164e2be0867.js" + } +} diff --git a/src/Symfony/Component/AssetMapper/Tests/fixtures/test_public/final-assets/manifest.json b/src/Symfony/Component/AssetMapper/Tests/fixtures/test_public/final-assets/manifest.json new file mode 100644 index 0000000000000..b32c6a99d4bef --- /dev/null +++ b/src/Symfony/Component/AssetMapper/Tests/fixtures/test_public/final-assets/manifest.json @@ -0,0 +1,3 @@ +{ + "file4.js": "/final-assets/file4.checksumfrommanifest.js" +} diff --git a/src/Symfony/Component/AssetMapper/composer.json b/src/Symfony/Component/AssetMapper/composer.json new file mode 100644 index 0000000000000..24fcdc65376df --- /dev/null +++ b/src/Symfony/Component/AssetMapper/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/asset-mapper", + "type": "library", + "description": "Maps directories of assets & makes them available in a public directory with versioned filenames.", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0" + }, + "require-dev": { + "symfony/asset": "^5.4|^6.0", + "symfony/browser-kit": "^5.4| 4027 ^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/framework-bundle": "^6.3", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\AssetMapper\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/AssetMapper/phpunit.xml.dist b/src/Symfony/Component/AssetMapper/phpunit.xml.dist new file mode 100644 index 0000000000000..21a1e9bf9ede4 --- /dev/null +++ b/src/Symfony/Component/AssetMapper/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Tests + ./vendor + + + From 98755a857746a9d53d42726bbba9695bbfffdcac Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 1 May 2023 08:24:47 +0200 Subject: [PATCH 474/475] Update CHANGELOG for 6.3.0-BETA1 --- CHANGELOG-6.3.md | 145 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 CHANGELOG-6.3.md diff --git a/CHANGELOG-6.3.md b/CHANGELOG-6.3.md new file mode 100644 index 0000000000000..44d759f00f7bf --- /dev/null +++ b/CHANGELOG-6.3.md @@ -0,0 +1,145 @@ +CHANGELOG for 6.3.x +=================== + +This changelog references the relevant changes (bug and security fixes) done +in 6.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/v6.3.0...v6.3.1 + +* 6.3.0-BETA1 (2023-05-01) + + * feature #49729 [Scheduler] Add a simple Scheduler class for when the component is used standalone (fabpot) + * feature #49725 [Messenger] Add support for the DelayStamp in InMemoryTransport (fabpot) + * feature #47112 [Messenger] Add a scheduler component (upyx, fabpot) + * feature #49691 [FrameworkBundle] Add scoped httplug clients and deprecate httplugs use like psr18 client (simonberger) + * feature #48542 [Webhook][RemoteEvent] Add the components (fabpot) + * feature #49620 [ErrorHander] Display exception properties in the HTML error page (lyrixx) + * feature #48128 [HttpFoundation] Add support for the 103 status code (Early Hints) and other 1XX statuses (dunglas) + * feature #48990 [DependencyInjection] deprecate the ``@required`` annotation (alexislefebvre) + * feature #49306 [Security] Add logout configuration for Clear-Site-Data header (maxbeckers) + * feature #49596 [Validator] Add the `exclude` option to the `Cascade` constraint (alexandre-daubois) + * feature #49291 [Serializer] Add methods `getSupportedTypes` to allow better performance (tucksaun, nicolas-grekas) + * feature #49642 [DependencyInjection] Deprecate `#[MapDecorated]` in favor of `#[AutowireDecorated]` (nicolas-grekas) + * feature #49539 [Messenger] make StopWorkerOnSignalsListener listen by default on SIGTERM and SIGINT (lyrixx) + * feature #49628 [DependencyInjection] Add support for autowiring services as closures using attributes (nicolas-grekas) + * feature #48992 [HttpKernel] Introduce pinnable value resolvers with `#[ValueResolver]` and `#[AsPinnedValueResolver]` (MatTheCat) + * feature #49121 [DomCrawler] Give choice of used parser (victor-prdh) + * feature #49610 [DoctrineBridge] deprecate doctrine schema subscribers in favor of listeners (alli83) + * feature #48821 [Serializer] add a context to allow invalid values in BackedEnumNormalizer (nikophil) + * feature #49529 [Console] Add support for managing exit code while handling signals (lyrixx) + * feature #49015 [Security] Added condition to always return the real Authenticator from security events (florentdestremau) + * feature #48899 [Security] Add remember me option for JSON logins (baumerdev, nicolas-grekas) + * feature #49302 [HttpClient] Add `UriTemplateHttpClient` (fancyweb) + * feature #49013 [Serializer] Replace the MissingConstructorArgumentsException class with MissingConstructorArgumentException (HypeMC) + * feature #49454 [Notifier] Add Pushover bridge (mocodo) + * feature #49461 [Mailer] Add MailerSend bridge (doobas) + * feature #49492 [DependencyInjection] Add support for Exclude attribute (lyrixx) + * feature #49139 [FrameworkBundle][HttpKernel] Display warmers duration on debug verbosity for `cache:clear` command (alexandre-daubois) + * feature #49417 [Validator] Add the option filenameMaxLength to the File constraint (Kevin Auvinet) + * feature #49487 [FrameworkBundle] Allow disabling dumping of container to XML to improve performance (ruudk) + * feature #49275 [FrameworkBundle][HttpKernel] Configure `ErrorHandler` on boot (HypeMC) + * feature #49464 [Validator] Implement countUnit option for Length constraint (spackmat) + * feature #49300 [Validator] Add a `NoSuspiciousCharacters` constraint to validate a string is not suspicious (MatTheCat) + * feature #49318 [HttpKernel] Add `skip_response_headers` to the `HttpCache` options (Toflar) + * feature #49428 [Messenger] Allow to define batch size when using `BatchHandlerTrait` with `getBatchSize()` (alexandre-daubois) + * feature #49429 [Mailer] Add option to enable Sandbox via dsn option sandbox=true (mdawart) + * feature #49433 [DependencyInjection] allow extending `Autowire` attribute (kbond) + * feature #49412 [DependencyInjection] Allow trimming service parameters value in XML configuration files (alexandre-daubois) + * feature #49442 [TwigBundle] Add alias deprecation for `Twig_Environment` (94noni) + * feature #49331 [PropertyAccess] Allow escaping in PropertyPath (alanpoulain) + * feature #49411 [DependencyInjection] Add AsAlias attribute (alanpoulain) + * feature #49343 [HtmlSanitizer] Remove experimental status (tgalopin) + * feature #49261 Smsapi - Make "from" optional (szal1k) + * feature #49327 [Notifier] Introduce FromNotificationInterface for MessageInterface implementations (fabpot) + * feature #49270 [Messenger] Allow passing a string instead of an array in `TransportNamesStamp` (alexandre-daubois) + * feature #49193 [Security] Return 403 instead of 500 when no firewall is defined (nicolas-grekas) + * feature #49098 [Config] Allow enum values in EnumNode (fancyweb) + * feature #49164 [Yaml] Feature #48920 Allow milliseconds and microseconds in dates (dustinwilson) + * feature #48981 [Console] Add ReStructuredText descriptor (danepowell) + * feature #48748 [VarDumper] Display invisible characters (alamirault) + * feature #48250 [Cache] Compatible with aliyun redis instance (tourze) + * feature #47066 [DependencyInjection] Allow attribute autoconfiguration on static methods (alex-dev) + * feature #49021 [SecurityBundle] Make firewalls event dispatcher traceable on debug mode (MatTheCat) + * feature #48930 [Cache] Add Redis Relay support (ostrolucky) + * feature #49102 [FrameworkBundle][Workflow] Register alias for argument for workflow services with workflow name only (lyrixx) + * feature #49064 [ExpressionLanguage] Deprecate loose comparisons when using the "in" operator (nicolas-grekas) + * feature #48999 [Lock] create migration for lock table when DoctrineDbalStore is used (alli83) + * feature #49011 [WebProfilerBundle] Close profiler settings on escape (norkunas) + * feature #48997 [WebProfilerBundle] Mailer panel tweaks (javiereguiluz) + * feature #49012 [WebProfilerBundle] Display date/time elements in the user local timezone (javiereguiluz) + * feature #48957 [Config] Do not array_unique EnumNode values (fancyweb) + * feature #48976 [ErrorHandler] try to read SYMFONY_PATCH_TYPE_DECLARATIONS from superglobal arrays too (xabbuh) + * feature #48938 [FrameworkBundle] Allow setting private services with the test container (nicolas-grekas) + * feature #48959 [Messenger] Allow password in redis dsn when using sockets (PhilETaylor) + * feature #48940 [DomCrawler] Add argument `$normalizeWhitespace` to `Crawler::innerText()` and make it return the first non-empty text (otsch) + * feature #48762 [WebProfilerBundle] Improve accessibility of tabs and some links (javiereguiluz) + * feature #48945 [WebProfilerBundle] Use a dynamic SVG favicon in the profiler (javiereguiluz) + * feature #48901 Allow Usage of ContentId in html (m42e) + * feature #48669 [ExpressionLanguage] Add `enum` expression function (alexandre-daubois) + * feature #48678 [FrameworkBundle] Rename service `notifier.logger_notification_listener` to `notifier.notification_logger_listener` (ker0x) + * feature #48516 [PhpUnitBridge] Add `enum_exists` mock (alexandre-daubois) + * feature #48855 [Notifier] Add new Symfony Notifier for PagerDuty (stloyd) + * feature #48876 [HttpKernel] Rename HttpStatus atribute to WithHttpStatus (fabpot) + * feature #48797 [FrameworkBundle] Add `extra` attribute for HttpClient Configuration (voodooism) + * feature #48747 [HttpKernel] Allow using `#[WithLogLevel]` for setting custom log level for exceptions (angelov) + * feature #48820 [HttpFoundation] ParameterBag::getEnum() (nikophil) + * feature #48685 [DependencyInjection] Exclude referencing service (self) in `TaggedIteratorArgument` (chalasr) + * feature #48409 [Mailer] add reject to `MessageEvent` to stop sending mail (Thomas Hanke, fabpot) + * feature #47709 [HttpFoundation] Add `StreamedJsonResponse` for efficient JSON streaming (alexander-schranz) + * feature #48810 Drop v1 contracts packages everywhere (derrabus) + * feature #48802 [DependencyInjection] Cut compilation time (nicolas-grekas) + * feature #48707 [DependencyInjection] Target Attribute must fail if the target does not exist (rodmen) + * feature #48387 [SecurityBundle] Rename `firewalls.logout.csrf_token_generator` to `firewalls.logout.csrf_token_manager` (MatTheCat) + * feature #48671 [Validator] Add `getConstraint()` method to `ConstraintViolationInterface` (syl20b) + * feature #48665 [FrameworkBundle] Deprecate `framework:exceptions` XML tag (MatTheCat) + * feature #48686 [DependencyInjection] Deprecate integer keys in "service_locator" config (upyx) + * feature #48616 [Notifier] GoogleChat CardsV1 is deprecated we must use cardsV2 instead (daifma) + * feature #48396 [Intl] Add a special locale to strip emojis easily with `EmojiTransliterator` (fancyweb) + * feature #48098 [HttpKernel]  Resolve DateTime value using the Clock (GromNaN) + * feature #48642 [Clock] Add `Clock` class and `now()` function (nicolas-grekas) + * feature #48531 [FrameworkBundle][Messenger] Add support for namespace wildcard in Messenger routing (brzuchal) + * feature #48121 [Messenger] Do not return fallback senders when other senders were already found (wouterj) + * feature #48582 [Security] Make login redirection logic available to programmatic login (hellomedia) + * feature #48352 [HttpKernel] Allow using `#[HttpStatus]` for setting status code and headers for HTTP exceptions (angelov) + * feature #48710 [DependencyInjection] Add support for nesting autowiring-related attributes into `#[Autowire(...)]` (nicolas-grekas) + * feature #48127 [Yaml] Add flag to dump numeric key as string (alamirault) + * feature #48696 [WebProfilerBundle] Add a title and img role to svg of the web debug toolbar (Monet Emilien) + * feature #48594 [SecurityBundle] Improve support for authenticators that don't need a user provider (wouterj) + * feature #48457 [FrameworkBundle] Improve UX ConfigDebugCommand has not yaml component (alamirault) + * feature #48044 [SecurityBundle] Set request stateless when firewall is stateless (alamirault) + * feature #48200 [Security] Allow custom user identifier for X509 authenticator (Spomky) + * feature #47352 [HttpKernel] FileProfilerStorage remove expired profiles mechanism (alamirault) + * feature #48614 [Messenger] Move Transport/InMemoryTransport to Transport/InMemory/InMemoryTransport (lyrixx) + * feature #48059 [HttpFoundation] Create migration for session table when pdo handler is used (alli83) + * feature #47349 [Notifier] Allow to update Slack messages (maxim-dovydenok-busuu) + * feature #48432 [VarDumper] Add support of named arguments to `dd()` and `dump()` to display a label (alexandre-daubois) + * feature #48275 [FrameworkBundle] Allow to avoid `limit` definition in a RateLimiter configuration when using the `no_limit` policy (alexandre-daubois) + * feature #39353 [FrameworkBundle][Notifier] Allow to configure or disable the message bus to use (jschaedl, fabpot) + * feature #48565 [Notifier] [FakeChat] Allow missing optional dependency (Benjamin Schoch) + * feature #48503 [Notifier] Add options to `SmsMessage` (gnito-org) + * feature #48164 [Serializer] Add encoder option for saving options (ihmels) + * feature #48206 [Console] Add placeholder formatters per ProgressBar instance (GromNaN) + * feature #48232 [Validator] Add `{{pattern}}` to `Regex` constraint violations (alamirault) + * feature #48299 [Console] #47809 remove exit() call in last SignalHandler (akuzia) + * feature #48424 [DomCrawler][FrameworkBundle] Add `assertSelectorCount` (curlycarla2004) + * feature #48546 [Notifier] [FakeSms] Allow missing optional dependency (Benjamin Schoch) + * feature #48484 [ProxyManagerBridge] Deprecate the package (nicolas-grekas) + * feature #48101 [Notifier] Add Mastodon Notifier (qdequippe) + * feature #48362 [Clock] Add ClockAwareTrait to help write time-sensitive classes (nicolas-grekas) + * feature #48478 [VarDumper] Add caster for WeakMap (nicolas-grekas) + * feature #47680 [DependencyInjection][HttpKernel] Introduce build parameters (HeahDude) + * feature #48374 [Notifier] [Telegram] Add support to answer callback queries (alexsoft) + * feature #48466 [Notifier] Add Line bridge (kurozumi) + * feature #48381 [Validator] Add `Uuid::TIME_BASED_VERSIONS` to match that a UUID being validated embeds a timestamp (alexandre-daubois) + * feature #48379 [HttpKernel] Set a default file link format when none is provided to FileLinkFormatter (alexandre-daubois) + * feature #48389 [Notifier] Add Bandwidth bridge (gnito-org) + * feature #48394 [Notifier] Add Plivo bridge (gnito-org) + * feature #48397 [Notifier] Add RingCentral bridge (gnito-org) + * feature #48398 [Notifier] Add Termii bridge (gnito-org) + * feature #48399 [Notifier] Add iSendPro bridge (leblanc-simon) + * feature #48084 [Notifier] Add Twitter notifier (nicolas-grekas) + * feature #48053 [Messenger] Improve DX (Nommyde) + * feature #48043 [SecurityBundle] Deprecate enabling bundle and not configuring it (alamirault) + * feature #48147 [DependencyInjection] Add `env` and `param` parameters for Autowire attribute (alexndlm) + From 94b719ec2ea1593cf3accf0c2c18e3ccf1ca8c47 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 1 May 2023 08:24:51 +0200 Subject: [PATCH 475/475] Update VERSION for 6.3.0-BETA1 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index f9a969b659130..c88671d8b4d13 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.3.0-DEV'; + public const VERSION = '6.3.0-BETA1'; public const VERSION_ID = 60300; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 3; public const RELEASE_VERSION = 0; - public const EXTRA_VERSION = 'DEV'; + public const EXTRA_VERSION = 'BETA1'; public const END_OF_MAINTENANCE = '01/2024'; public const END_OF_LIFE = '01/2024'; 0