diff --git a/CHANGELOG-6.2.md b/CHANGELOG-6.2.md index 4789556444de9..311021e53c64e 100644 --- a/CHANGELOG-6.2.md +++ b/CHANGELOG-6.2.md @@ -7,6 +7,51 @@ in 6.2 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v6.2.0...v6.2.1 +* 6.2.2 (2022-12-16) + + * bug #48661 [Serializer] fix context attribute with serializedName (nikophil) + * bug #48681 [Console] Revert "bug #48089 Fix clear line with question in section (maxbeckers) (chalasr) + * bug #48680 [Cache] fix lazyness of redis when using RedisTagAwareAdapter (nicolas-grekas) + * bug #48651 [HttpKernel] AbstractSessionListener should not override the cache lifetime for private responses (rodmen) + * bug #48591 [DependencyInjection] Shared private services becomes public after a public service is accessed (alexpott) + * bug #48126 [Mailer] Include all transports' debug messages in RoundRobin transport exception (mixdf) + * bug #48644 [Validator] Allow opt-out of EmailValidator deprecation when using Validation::createValidatorBuilder() (nicolas-grekas) + * bug #48606 [FrameworkBundle] container:debug CLI output improvements for excluded services (apfelbox) + * bug #48089 [Console] Fix clear line with question in section (maxbeckers) + * bug #48602 [HtmlSanitizer] Fix HtmlSanitizer default configuration behavior for allowed schemes (Titouan Galopin) + * bug #48635 [HttpFoundation] Use relative timestamps with MemcachedSessionHandler (tvlooy) + * bug #47979 [Cache] Fix dealing with ext-redis' multi/exec returning a bool (João Nogueira) + * bug #48612 [Messenger] [Amqp] Added missing rpc_timeout option (lyrixx) + * bug #48233 [Serializer] Prevent `GetSetMethodNormalizer` from creating invalid magic method call (klaussilveira) + * bug #48628 [HttpFoundation] Fix dumping array cookies (nicolas-grekas) + * bug #48559 [ExpressionLanguage] Fix BC of cached SerializedParsedExpression containing GetAttrNode (fancyweb) + * bug #48524 [HttpKernel] Fix `CacheAttributeListener` priority (HypeMC) + * bug #48451 [Translation] Fix extraction when dealing with VariadicPlaceholder parameters (Kocal) + * bug #48601 [SecurityBundle] Fix authenticator existence check in `Security::login()` (chalasr) + * bug #48587 [TwigBundle] Alias BodyRendererInterface (n3o77) + * bug #48580 [Console] Fix missing command not matching namespace error message (Titouan Galopin) + * bug #48449 [DependencyInjection] Fix bug when tag name is a text node (BrandonlinU) + * bug #48048 [WebProfilerBundle] Fix dump header not being displayed (HypeMC) + * bug #47836 [HttpClient] TraceableHttpClient: increase decorator's priority (adpeyre) + * bug #48259 [FrameworkBundle] Allow configuring `framework.exceptions` with a config builder (MatTheCat) + * bug #48314 [Mime] Fix MessagePart serialization (Amunak) + * bug #48331 [Yaml] fix dumping top-level tagged values (xabbuh) + * bug #48615 Fix getting the name of closures on PHP 8.1.11+ (nicolas-grekas) + * bug #48624 [ErrorHandler][HttpKernel] Fix reading the SYMFONY_IDE env var (nicolas-grekas) + * bug #48618 [ErrorHandler] [DebugClassLoader] Fix some new return types support (fancyweb) + * bug #48605 [VarExporter] Fix adding a key to an uninitialized array (nicolas-grekas) + * bug #48554 [Security] Fix invalid deprecation messages in Security constants (IonBazan) + * bug #48538 [Clock] Fix `usleep` deprecation warning (victor-prdh) + * bug #48421 [HttpFoundation] IPv4-mapped IPv6 addresses incorrectly rejected (bonroyage) + * bug #48501 [RateLimiter] Add `int` to `Reservation::wait()` (DaRealFreak) + * bug #48359 [VarDumper] Ignore \Error in __debugInfo() (fancyweb) + * bug #48553 [VarExporter] Fix calling `parent::__wakeup()` when unserializing with LazyProxyTrait (azjezz) + * bug #48489 [DoctrineBridge] Skip resolving entities when the corresponding request attribute is already an object (nicolas-grekas) + * bug #48534 [FrameworkBundle] add `kernel.locale_aware` tag to `LocaleSwitcher` (kbond) + * bug #48521 [FrameworkBundle] fix removing commands if console not available (kbond) + * bug #48522 [DependencyInjection] Generate different classes for ghost objects and virtual proxies (nicolas-grekas) + * bug #48482 [DependencyInjection] Revert "bug #48027 Don't autoconfigure tag when it's already set with attributes" (nicolas-grekas) + * 6.2.1 (2022-12-06) * bug #48502 [DependencyInjection] Fix `ContainerBuilder` stats env usage with enum (alamirault) @@ -22,9 +67,6 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c * bug #48046 [WebProfilerBundle] Remove redundant code from logger template (HypeMC) * bug #48428 Fixed undefined variable error (Kevin Meijer) * bug #48416 [FrameworkBundle] don't register the MailerTestCommand symfony/console is not installed (xabbuh) - * bug #48395 [String] Fix AsciiSlugger with emojis (fancyweb) - * bug #48385 [Security] Reuse `AbstractFactory`'s config tree in `AccessTokenFactory` (chalasr) - * bug #48292 [Security] [LoginLink] Throw InvalidLoginLinkException on missing parameter (MatTheCat) * 6.2.0 (2022-11-30) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5f5207576f4f6..7d9901aeb3fa1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,6 +13,7 @@ + diff --git a/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php b/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php index 5794d9814215d..b531857c1422c 100644 --- a/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php +++ b/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php @@ -40,7 +40,13 @@ public function __construct( public function resolve(Request $request, ArgumentMetadata $argument): array { - $options = $this->getMapEntityAttribute($argument); + if (\is_object($request->attributes->get($argument->getName()))) { + return []; + } + + $options = $argument->getAttributes(MapEntity::class, ArgumentMetadata::IS_INSTANCEOF); + $options = ($options[0] ?? $this->defaults)->withDefaults($this->defaults, $argument->getType()); + if (!$options->class || $options->disabled) { return []; } @@ -201,12 +207,4 @@ private function findViaExpression(ObjectManager $manager, Request $request, Map return null; } } - - private function getMapEntityAttribute(ArgumentMetadata $argument): MapEntity - { - /** @var MapEntity $options */ - $options = $argument->getAttributes(MapEntity::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? $this->defaults; - - return $options->withDefaults($this->defaults, $argument->getType()); - } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php b/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php index 629a37256c0f2..3dc814acaa047 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php @@ -373,6 +373,20 @@ public function testExpressionSyntaxErrorThrowsException() $resolver->resolve($request, $argument); } + public function testAlreadyResolved() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $request->attributes->set('arg', new \stdClass()); + + $argument = $this->createArgument('stdClass', name: 'arg'); + + $this->assertSame([], $resolver->resolve($request, $argument)); + } + private function createArgument(string $class = null, MapEntity $entity = null, string $name = 'arg', bool $isNullable = false): ArgumentMetadata { return new ArgumentMetadata($name, $class ?? \stdClass::class, false, false, null, $isNullable, $entity ? [$entity] : []); diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/HttpCodeActivationStrategyTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/HttpCodeActivationStrategyTest.php index 81613fe21e0e0..a5c877b296681 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/HttpCodeActivationStrategyTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/HttpCodeActivationStrategyTest.php @@ -60,15 +60,15 @@ public function isActivatedProvider(): array { return [ ['/test', RecordFactory::create(Logger::ERROR), true], - ['/400', RecordFactory::create(Logger::ERROR, context: $this->getContextException(400)), true], - ['/400/a', RecordFactory::create(Logger::ERROR, context: $this->getContextException(400)), false], - ['/400/b', RecordFactory::create(Logger::ERROR, context: $this->getContextException(400)), false], - ['/400/c', RecordFactory::create(Logger::ERROR, context: $this->getContextException(400)), true], - ['/401', RecordFactory::create(Logger::ERROR, context: $this->getContextException(401)), true], - ['/403', RecordFactory::create(Logger::ERROR, context: $this->getContextException(403)), false], - ['/404', RecordFactory::create(Logger::ERROR, context: $this->getContextException(404)), false], - ['/405', RecordFactory::create(Logger::ERROR, context: $this->getContextException(405)), false], - ['/500', RecordFactory::create(Logger::ERROR, context: $this->getContextException(500)), true], + ['/400', RecordFactory::create(Logger::ERROR, context: self::getContextException(400)), true], + ['/400/a', RecordFactory::create(Logger::ERROR, context: self::getContextException(400)), false], + ['/400/b', RecordFactory::create(Logger::ERROR, context: self::getContextException(400)), false], + ['/400/c', RecordFactory::create(Logger::ERROR, context: self::getContextException(400)), true], + ['/401', RecordFactory::create(Logger::ERROR, context: self::getContextException(401)), true], + ['/403', RecordFactory::create(Logger::ERROR, context: self::getContextException(403)), false], + ['/404', RecordFactory::create(Logger::ERROR, context: self::getContextException(404)), false], + ['/405', RecordFactory::create(Logger::ERROR, context: self::getContextException(405)), false], + ['/500', RecordFactory::create(Logger::ERROR, context: self::getContextException(500)), true], ]; } diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php index f58e9afa7164e..220cbe5a78bcd 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php @@ -40,18 +40,18 @@ public function isActivatedProvider(): array { return [ ['/test', RecordFactory::create(Logger::DEBUG), false], - ['/foo', RecordFactory::create(Logger::DEBUG, context: $this->getContextException(404)), false], - ['/baz/bar', RecordFactory::create(Logger::ERROR, context: $this->getContextException(404)), false], - ['/foo', RecordFactory::create(Logger::ERROR, context: $this->getContextException(404)), false], - ['/foo', RecordFactory::create(Logger::ERROR, context: $this->getContextException(500)), true], + ['/foo', RecordFactory::create(Logger::DEBUG, context: self::getContextException(404)), false], + ['/baz/bar', RecordFactory::create(Logger::ERROR, context: self::getContextException(404)), false], + ['/foo', RecordFactory::create(Logger::ERROR, context: self::getContextException(404)), false], + ['/foo', RecordFactory::create(Logger::ERROR, context: self::getContextException(500)), true], ['/test', RecordFactory::create(Logger::ERROR), true], - ['/baz', RecordFactory::create(Logger::ERROR, context: $this->getContextException(404)), true], - ['/baz', RecordFactory::create(Logger::ERROR, context: $this->getContextException(500)), true], + ['/baz', RecordFactory::create(Logger::ERROR, context: self::getContextException(404)), true], + ['/baz', RecordFactory::create(Logger::ERROR, context: self::getContextException(500)), true], ]; } - protected function getContextException(int $code): array + protected static function getContextException(int $code): array { return ['exception' => new HttpException($code)]; } diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php index 8de9a956e7282..6e4b67e265d1d 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php @@ -46,8 +46,8 @@ public function testDatetimeRfc3339Format() public function testDebugProcessor() { $processor = new DebugProcessor(); - $processor($this->getRecord()); - $processor($this->getRecord(Logger::ERROR)); + $processor(self::getRecord()); + $processor(self::getRecord(Logger::ERROR)); $this->assertCount(2, $processor->getLogs()); $this->assertSame(1, $processor->countErrors()); @@ -65,8 +65,8 @@ public function testWithRequestStack() { $stack = new RequestStack(); $processor = new DebugProcessor($stack); - $processor($this->getRecord()); - $processor($this->getRecord(Logger::ERROR)); + $processor(self::getRecord()); + $processor(self::getRecord(Logger::ERROR)); $this->assertCount(2, $processor->getLogs()); $this->assertSame(1, $processor->countErrors()); @@ -74,8 +74,8 @@ public function testWithRequestStack() $request = new Request(); $stack->push($request); - $processor($this->getRecord()); - $processor($this->getRecord(Logger::ERROR)); + $processor(self::getRecord()); + $processor(self::getRecord(Logger::ERROR)); $this->assertCount(4, $processor->getLogs()); $this->assertSame(2, $processor->countErrors()); @@ -99,7 +99,7 @@ public function testInheritedClassCallCountErrorsWithoutArgument() $this->assertEquals(0, $debugProcessorChild->countErrors()); } - private function getRecord($level = Logger::WARNING, $message = 'test'): array|LogRecord + private static function getRecord($level = Logger::WARNING, $message = 'test'): array|LogRecord { return RecordFactory::create($level, $message); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index 628cb7cecf6c7..28f991a05c40d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -64,7 +64,7 @@ protected function configure() For dumping a specific option, add its path as second argument (only available for the yaml format): - php %command.full_name% framework profiler.matcher + php %command.full_name% framework http_client.default_options EOF ) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index b040f2419d1b3..293fccc20b18b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -308,6 +308,9 @@ private function findServiceIdsContaining(ContainerBuilder $builder, string $nam if (!$showHidden && str_starts_with($serviceId, '.')) { continue; } + if (!$showHidden && $builder->hasDefinition($serviceId) && $builder->getDefinition($serviceId)->hasTag('container.excluded')) { + continue; + } if (false !== stripos(str_replace('\\', '', $serviceId), $name)) { $foundServiceIdsIgnoringBackslashes[] = $serviceId; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index b47b37c7398fc..c454b85ffb4bf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -370,7 +370,7 @@ private function getCallableData(mixed $callable): array } $data['name'] = $r->name; - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $data['class'] = $class->name; if (!$r->getClosureThis()) { $data['static'] = true; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 555911a761506..36b297172681f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -387,7 +387,7 @@ protected function describeCallable(mixed $callable, array $options = []) } $string .= "\n".sprintf('- Name: `%s`', $r->name); - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $string .= "\n".sprintf('- Class: `%s`', $class->name); if (!$r->getClosureThis()) { $string .= "\n- Static: yes"; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 1fb75fbbf6c35..4217a43b15414 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -617,7 +617,7 @@ private function formatCallable(mixed $callable): string if (str_contains($r->name, '{closure}')) { return 'Closure()'; } - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { return sprintf('%s::%s()', $class->name, $r->name); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index 955a278d86ebb..6c01c15ce04db 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -565,7 +565,7 @@ private function getCallableDocument(mixed $callable): \DOMDocument } $callableXML->setAttribute('name', $r->name); - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $callableXML->setAttribute('class', $class->name); if (!$r->getClosureThis()) { $callableXML->setAttribute('static', 'true'); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index bcddb7115bb73..b986da0e6d8c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1189,35 +1189,31 @@ private function addExceptionsSection(ArrayNodeDefinition $rootNode) $logLevels = (new \ReflectionClass(LogLevel::class))->getConstants(); $rootNode + ->fixXmlConfig('exception') ->children() ->arrayNode('exceptions') ->info('Exception handling configuration') + ->useAttributeAsKey('class') ->beforeNormalization() + // Handle legacy XML configuration ->ifArray() ->then(function (array $v): array { if (!\array_key_exists('exception', $v)) { return $v; } - // Fix XML normalization - $data = isset($v['exception'][0]) ? $v['exception'] : [$v['exception']]; - $exceptions = []; - foreach ($data as $exception) { - $config = []; - if (\array_key_exists('log-level', $exception)) { - $config['log_level'] = $exception['log-level']; - } - if (\array_key_exists('status-code', $exception)) { - $config['status_code'] = $exception['status-code']; - } - $exceptions[$exception['name']] = $config; + $v = $v['exception']; + unset($v['exception']); + + foreach ($v as &$exception) { + $exception['class'] = $exception['name']; + unset($exception['name']); } - return $exceptions; + return $v; }) ->end() ->prototype('array') - ->fixXmlConfig('exception') ->children() ->scalarNode('log_level') ->info('The level of log message. Null to let Symfony decide.') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 9fbfa7a6a3d07..633cbe60de269 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -390,7 +390,7 @@ public function load(array $configs, ContainerBuilder $container) if ($this->readConfigEnabled('mailer', $container, $config['mailer'])) { $this->registerMailerConfiguration($config['mailer'], $container, $loader); - if (!class_exists(MailerTestCommand::class) || !$this->hasConsole()) { + if (!$this->hasConsole() || !class_exists(MailerTestCommand::class)) { $container->removeDefinition('console.command.mailer_test'); } } @@ -1971,7 +1971,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder throw new LogicException('Messenger support cannot be enabled as the Messenger component is not installed. Try running "composer require symfony/messenger".'); } - if (!class_exists(StatsCommand::class)) { + if (!$this->hasConsole() || !class_exists(StatsCommand::class)) { $container->removeDefinition('console.command.messenger_stats'); } @@ -2787,10 +2787,14 @@ private function registerHtmlSanitizerConfiguration(array $config, ContainerBuil // Settings $def->addMethodCall('forceHttpsUrls', [$sanitizerConfig['force_https_urls']], true); - $def->addMethodCall('allowLinkSchemes', [$sanitizerConfig['allowed_link_schemes']], true); + if ($sanitizerConfig['allowed_link_schemes']) { + $def->addMethodCall('allowLinkSchemes', [$sanitizerConfig['allowed_link_schemes']], true); + } $def->addMethodCall('allowLinkHosts', [$sanitizerConfig['allowed_link_hosts']], true); $def->addMethodCall('allowRelativeLinks', [$sanitizerConfig['allow_relative_links']], true); - $def->addMethodCall('allowMediaSchemes', [$sanitizerConfig['allowed_media_schemes']], true); + if ($sanitizerConfig['allowed_media_schemes']) { + $def->addMethodCall('allowMediaSchemes', [$sanitizerConfig['allowed_media_schemes']], true); + } $def->addMethodCall('allowMediaHosts', [$sanitizerConfig['allowed_media_hosts']], true); $def->addMethodCall('allowRelativeMedias', [$sanitizerConfig['allow_relative_medias']], true); 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 7862badd991fe..d1f547f70778a 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 @@ -30,6 +30,7 @@ + @@ -365,14 +366,29 @@ - + - - + - + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php index 280e03f355cd4..dcfa2bc15716d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php @@ -186,10 +186,11 @@ ->set('translation.locale_switcher', LocaleSwitcher::class) ->args([ param('kernel.default_locale'), - tagged_iterator('kernel.locale_aware'), + tagged_iterator('kernel.locale_aware', exclude: 'translation.locale_switcher'), service('router.request_context')->ignoreOnInvalid(), ]) ->tag('kernel.reset', ['method' => 'reset']) + ->tag('kernel.locale_aware') ->alias(LocaleAwareInterface::class, 'translation.locale_switcher') ->alias(LocaleSwitcher::class, 'translation.locale_switcher') ; diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php index 1a629d6255fbe..d6b29d2b5a0c6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php @@ -123,10 +123,6 @@ private static function getMessageMailerEvents(): MessageEvents return $container->get('mailer.message_logger_listener')->getEvents(); } - if ($container->has('mailer.logger_message_listener')) { - return $container->get('mailer.logger_message_listener')->getEvents(); - } - static::fail('A client must have Mailer enabled to make email assertions. Did you forget to require symfony/mailer?'); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions.xml index 2e6048fa7c7aa..4a877c1eb3499 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions.xml @@ -6,11 +6,25 @@ 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/exceptions_legacy.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions_legacy.xml new file mode 100644 index 0000000000000..2e6048fa7c7aa --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions_legacy.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 640a152402bea..945cd330fa94e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -37,6 +37,7 @@ use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass; +use Symfony\Component\DependencyInjection\Compiler\ResolveTaggedIteratorArgumentPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; @@ -579,24 +580,34 @@ public function testExceptionsConfig() { $container = $this->createContainerFromFile('exceptions'); + $configuration = $container->getDefinition('exception_listener')->getArgument(3); + $this->assertSame([ - \Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class => [ - 'log_level' => 'info', - 'status_code' => 422, - ], - \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class => [ - 'log_level' => 'info', - 'status_code' => null, - ], - \Symfony\Component\HttpKernel\Exception\ConflictHttpException::class => [ - 'log_level' => 'info', - 'status_code' => null, - ], - \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class => [ - 'log_level' => null, - 'status_code' => 500, - ], - ], $container->getDefinition('exception_listener')->getArgument(3)); + \Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class, + \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class, + \Symfony\Component\HttpKernel\Exception\ConflictHttpException::class, + \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class, + ], array_keys($configuration)); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => 422, + ], $configuration[\Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => null, + ], $configuration[\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => null, + ], $configuration[\Symfony\Component\HttpKernel\Exception\ConflictHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => null, + 'status_code' => 500, + ], $configuration[\Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class]); } public function testRouter() @@ -2067,7 +2078,9 @@ public function testLocaleSwitcherServiceRegistered() $this->markTestSkipped('LocaleSwitcher not available.'); } - $container = $this->createContainerFromFile('full'); + $container = $this->createContainerFromFile('full', compile: false); + $container->addCompilerPass(new ResolveTaggedIteratorArgumentPass()); + $container->compile(); $this->assertTrue($container->has('translation.locale_switcher')); @@ -2077,6 +2090,10 @@ public function testLocaleSwitcherServiceRegistered() $this->assertInstanceOf(TaggedIteratorArgument::class, $switcherDef->getArgument(1)); $this->assertSame('kernel.locale_aware', $switcherDef->getArgument(1)->getTag()); $this->assertEquals(new Reference('router.request_context', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $switcherDef->getArgument(2)); + + $localeAwareServices = array_map(fn (Reference $r) => (string) $r, $switcherDef->getArgument(1)->getValues()); + + $this->assertNotContains('translation.locale_switcher', $localeAwareServices); } public function testHtmlSanitizer() @@ -2143,7 +2160,9 @@ public function testHtmlSanitizerDefaultNullAllowedLinkMediaHost() $calls = $container->getDefinition('html_sanitizer.config.custom_default')->getMethodCalls(); $this->assertContains(['allowLinkHosts', [null], true], $calls); + $this->assertContains(['allowRelativeLinks', [false], true], $calls); $this->assertContains(['allowMediaHosts', [null], true], $calls); + $this->assertContains(['allowRelativeMedias', [false], true], $calls); } public function testHtmlSanitizerDefaultConfig() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php index ebc37d93bed84..131bb07f0c657 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php @@ -32,4 +32,38 @@ public function testMessengerMiddlewareFactoryErroneousFormat() { $this->markTestSkipped('XML configuration will not allow erroneous format.'); } + + public function testLegacyExceptionsConfig() + { + $container = $this->createContainerFromFile('exceptions_legacy'); + + $configuration = $container->getDefinition('exception_listener')->getArgument(3); + + $this->assertSame([ + \Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class, + \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class, + \Symfony\Component\HttpKernel\Exception\ConflictHttpException::class, + \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class, + ], array_keys($configuration)); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => 422, + ], $configuration[\Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => null, + ], $configuration[\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => null, + ], $configuration[\Symfony\Component\HttpKernel\Exception\ConflictHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => null, + 'status_code' => 500, + ], $configuration[\Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class]); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CacheAttributeListenerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CacheAttributeListenerTest.php new file mode 100644 index 0000000000000..72b2c12266d87 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CacheAttributeListenerTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\Cache; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Security\Core\User\InMemoryUser; +use Symfony\Component\Security\Http\Attribute\IsGranted; + +class CacheAttributeListenerTest extends AbstractWebTestCase +{ + public function testAnonimousUserWithEtag() + { + $client = self::createClient(['test_case' => 'CacheAttributeListener']); + + $client->request('GET', '/', server: ['HTTP_IF_NONE_MATCH' => sprintf('"%s"', hash('sha256', '12345'))]); + + self::assertTrue($client->getResponse()->isRedirect('http://localhost/login')); + } + + public function testAnonimousUserWithoutEtag() + { + $client = self::createClient(['test_case' => 'CacheAttributeListener']); + + $client->request('GET', '/'); + + self::assertTrue($client->getResponse()->isRedirect('http://localhost/login')); + } + + public function testLoggedInUserWithEtag() + { + $client = self::createClient(['test_case' => 'CacheAttributeListener']); + + $client->loginUser(new InMemoryUser('the-username', 'the-password', ['ROLE_USER'])); + $client->request('GET', '/', server: ['HTTP_IF_NONE_MATCH' => sprintf('"%s"', hash('sha256', '12345'))]); + + $response = $client->getResponse(); + + self::assertSame(304, $response->getStatusCode()); + self::assertSame('', $response->getContent()); + } + + public function testLoggedInUserWithoutEtag() + { + $client = self::createClient(['test_case' => 'CacheAttributeListener']); + + $client->loginUser(new InMemoryUser('the-username', 'the-password', ['ROLE_USER'])); + $client->request('GET', '/'); + + $response = $client->getResponse(); + + self::assertSame(200, $response->getStatusCode()); + self::assertSame('Hi there!', $response->getContent()); + } +} + +class TestEntityValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): iterable + { + return Post::class === $argument->getType() ? [new Post()] : []; + } +} + +class Post +{ + public function getId(): int + { + return 1; + } + + public function getEtag(): string + { + return '12345'; + } +} + +class WithAttributesController +{ + #[IsGranted('ROLE_USER')] + #[Cache(etag: 'post.getEtag()')] + public function __invoke(Post $post): Response + { + return new Response('Hi there!'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CacheAttributeListener/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CacheAttributeListener/bundles.php new file mode 100644 index 0000000000000..9a26fb163a77d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CacheAttributeListener/bundles.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\SecurityBundle\SecurityBundle; + +return [ + new FrameworkBundle(), + new SecurityBundle(), +]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CacheAttributeListener/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CacheAttributeListener/config.yml new file mode 100644 index 0000000000000..21890451a1094 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CacheAttributeListener/config.yml @@ -0,0 +1,24 @@ +imports: + - { resource: ../config/default.yml } + +services: + Symfony\Bundle\FrameworkBundle\Tests\Functional\TestEntityValueResolver: + tags: + - { name: controller.argument_value_resolver, priority: 110 } + + Symfony\Bundle\FrameworkBundle\Tests\Functional\WithAttributesController: + public: true + +security: + providers: + main: + memory: + users: + the-username: { password: the-password, roles: [ 'ROLE_USER' ] } + + firewalls: + main: + pattern: ^/ + form_login: + login_path: /login + provider: main diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CacheAttributeListener/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CacheAttributeListener/routing.yml new file mode 100644 index 0000000000000..50c37b823fcbe --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CacheAttributeListener/routing.yml @@ -0,0 +1,3 @@ +with_attributes_controller: + path: / + controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\WithAttributesController diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 4ab6b2e919e32..00986c7854f08 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -26,7 +26,7 @@ "symfony/error-handler": "^6.1", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/http-foundation": "^6.2", - "symfony/http-kernel": "^6.2", + "symfony/http-kernel": "^6.2.1", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", diff --git a/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist b/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist index d00ee0f1e214e..3ea6d9e6e218e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist +++ b/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist @@ -10,6 +10,7 @@ > + diff --git a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php index 757f61629f6f5..8a87422862287 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php @@ -245,7 +245,7 @@ private function formatCallable(mixed $callable): string if (str_contains($r->name, '{closure}')) { return 'Closure()'; } - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { return sprintf('%s::%s()', $class->name, $r->name); } diff --git a/src/Symfony/Bundle/SecurityBundle/Security.php b/src/Symfony/Bundle/SecurityBundle/Security.php index 05a5f7c9394eb..d78cfc0edd02f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security.php +++ b/src/Symfony/Bundle/SecurityBundle/Security.php @@ -24,6 +24,7 @@ use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Event\LogoutEvent; use Symfony\Component\Security\Http\ParameterBagUtils; +use Symfony\Component\Security\Http\SecurityRequestAttributes; use Symfony\Contracts\Service\ServiceProviderInterface; /** @@ -37,6 +38,10 @@ */ class Security extends LegacySecurity { + public const ACCESS_DENIED_ERROR = SecurityRequestAttributes::ACCESS_DENIED_ERROR; + public const AUTHENTICATION_ERROR = SecurityRequestAttributes::AUTHENTICATION_ERROR; + public const LAST_USERNAME = SecurityRequestAttributes::LAST_USERNAME; + public function __construct(private readonly ContainerInterface $container, private readonly array $authenticators = []) { parent::__construct($container, false); @@ -111,7 +116,7 @@ public function logout(bool $validateCsrfToken = true): ?Response private function getAuthenticator(?string $authenticatorName, string $firewallName): AuthenticatorInterface { - if (!\array_key_exists($firewallName, $this->authenticators)) { + if (!isset($this->authenticators[$firewallName])) { throw new LogicException(sprintf('No authenticators found for firewall "%s".', $firewallName)); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php index 7ee7a2465263d..b69467cb95cf3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php @@ -169,6 +169,38 @@ public function testLogin() $security->login($user); } + public function testLoginWithoutAuthenticatorThrows() + { + $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); + + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['request_stack', $requestStack], + ['security.firewall.map', $firewallMap], + ['security.user_checker', $userChecker], + ]) + ; + + $requestStack->expects($this->once())->method('getCurrentRequest')->willReturn($request); + $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewall); + + $security = new Security($container, ['main' => null]); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('No authenticators found for firewall "main".'); + + $security->login($user); + } + public function testLogout() { $request = new Request(); diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/mailer.php b/src/Symfony/Bundle/TwigBundle/Resources/config/mailer.php index 1444481f2c0ba..e43658a7da02c 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/mailer.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/mailer.php @@ -13,6 +13,7 @@ use Symfony\Bridge\Twig\Mime\BodyRenderer; use Symfony\Component\Mailer\EventListener\MessageListener; +use Symfony\Component\Mime\BodyRendererInterface; return static function (ContainerConfigurator $container) { $container->services() @@ -22,5 +23,6 @@ ->set('twig.mime_body_renderer', BodyRenderer::class) ->args([service('twig')]) + ->alias(BodyRendererInterface::class, 'twig.mime_body_renderer') ; }; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php index fbaf2f7965d05..c345b5fbb89c8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php @@ -66,10 +66,10 @@ public function provideRequestAndResponses() ]; return [ - [$nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], $this->createRequest(), $this->createResponse()], - [$nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], $this->createRequest($requestNonceHeaders), $this->createResponse($responseNonceHeaders)], - [$nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], $this->createRequest($requestNonceHeaders), $this->createResponse()], - [$nonce, ['csp_script_nonce' => $responseScriptNonce, 'csp_style_nonce' => $responseStyleNonce], $this->createRequest(), $this->createResponse($responseNonceHeaders)], + [$nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], self::createRequest(), self::createResponse()], + [$nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], self::createRequest($requestNonceHeaders), self::createResponse($responseNonceHeaders)], + [$nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], self::createRequest($requestNonceHeaders), self::createResponse()], + [$nonce, ['csp_script_nonce' => $responseScriptNonce, 'csp_style_nonce' => $responseStyleNonce], self::createRequest(), self::createResponse($responseNonceHeaders)], ]; } @@ -96,104 +96,104 @@ public function provideRequestAndResponsesForOnKernelResponse() [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(), + self::createRequest(), + self::createResponse(), ['Content-Security-Policy' => null, 'Content-Security-Policy-Report-Only' => null, 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], - $this->createRequest($requestNonceHeaders), - $this->createResponse($responseNonceHeaders), + self::createRequest($requestNonceHeaders), + self::createResponse($responseNonceHeaders), ['Content-Security-Policy' => null, 'Content-Security-Policy-Report-Only' => null, 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], - $this->createRequest($requestNonceHeaders), - $this->createResponse(), + self::createRequest($requestNonceHeaders), + self::createResponse(), ['Content-Security-Policy' => null, 'Content-Security-Policy-Report-Only' => null, 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $responseScriptNonce, 'csp_style_nonce' => $responseStyleNonce], - $this->createRequest(), - $this->createResponse($responseNonceHeaders), + self::createRequest(), + self::createResponse($responseNonceHeaders), ['Content-Security-Policy' => null, 'Content-Security-Policy-Report-Only' => null, 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'frame-ancestors https: ; form-action: https:', 'Content-Security-Policy-Report-Only' => 'frame-ancestors http: ; form-action: http:']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'frame-ancestors https: ; form-action: https:', 'Content-Security-Policy-Report-Only' => 'frame-ancestors http: ; form-action: http:']), ['Content-Security-Policy' => 'frame-ancestors https: ; form-action: https:', 'Content-Security-Policy-Report-Only' => 'frame-ancestors http: ; form-action: http:', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'']), ['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; style-src \'self\' domain.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; style-src \'self\' domain-report-only.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\'']), ['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'default-src \'none\'', 'Content-Security-Policy-Report-Only' => 'default-src \'none\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'default-src \'none\'', 'Content-Security-Policy-Report-Only' => 'default-src \'none\'']), ['Content-Security-Policy' => 'default-src \'none\'; script-src \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy-Report-Only' => 'default-src \'none\'; script-src \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'']), ['Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'script-src \'self\'; style-src \'self\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'script-src \'self\'; style-src \'self\'']), ['Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'']), + self::createRequest(), + self::createResponse(['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'']), ['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'', 'Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['X-Content-Security-Policy' => 'script-src \'self\'']), + self::createRequest(), + self::createResponse(['X-Content-Security-Policy' => 'script-src \'self\'']), ['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'sha384-LALALALALAAL\'']), + self::createRequest(), + self::createResponse(['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'sha384-LALALALALAAL\'']), ['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'sha384-LALALALALAAL\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'script-src \'self\'; style-src \'self\'', 'X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'; style-src \'self\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'script-src \'self\'; style-src \'self\'', 'X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'; style-src \'self\'']), ['Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'; style-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\''], ], ]; } - private function createRequest(array $headers = []) + private static function createRequest(array $headers = []) { $request = new Request(); $request->headers->add($headers); @@ -201,7 +201,7 @@ private function createRequest(array $headers = []) return $request; } - private function createResponse(array $headers = []) + private static function createResponse(array $headers = []) { $response = new Response(); $response->headers->add($headers); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php index 0038736820388..fbfd2ed586f87 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php @@ -33,8 +33,12 @@ class WebProfilerExtensionTest extends TestCase public static function assertSaneContainer(Container $container) { + $removedIds = $container->getRemovedIds(); $errors = []; foreach ($container->getServiceIds() as $id) { + if (isset($removedIds[$id])) { + continue; + } try { $container->get($id); } catch (\Exception $e) { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Twig/WebProfilerExtensionTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Twig/WebProfilerExtensionTest.php new file mode 100644 index 0000000000000..6b026bcc53385 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Twig/WebProfilerExtensionTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Tests\Twig; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\WebProfilerBundle\Twig\WebProfilerExtension; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Twig\Environment; +use Twig\Extension\CoreExtension; +use Twig\Extension\EscaperExtension; + +class WebProfilerExtensionTest extends TestCase +{ + /** + * @dataProvider provideMessages + */ + public function testDumpHeaderIsDisplayed(string $message, array $context, bool $dump1HasHeader, bool $dump2HasHeader) + { + class_exists(CoreExtension::class); // Load twig_convert_encoding() + class_exists(EscaperExtension::class); // Load twig_escape_filter() + + $twigEnvironment = $this->mockTwigEnvironment(); + $varCloner = new VarCloner(); + + $webProfilerExtension = new WebProfilerExtension(); + + $needle = 'window.Sfdump'; + + $dump1 = $webProfilerExtension->dumpLog($twigEnvironment, $message, $varCloner->cloneVar($context)); + self::assertSame($dump1HasHeader, str_contains($dump1, $needle)); + + $dump2 = $webProfilerExtension->dumpData($twigEnvironment, $varCloner->cloneVar([])); + self::assertSame($dump2HasHeader, str_contains($dump2, $needle)); + } + + public function provideMessages(): iterable + { + yield ['Some message', ['foo' => 'foo', 'bar' => 'bar'], false, true]; + yield ['Some message {@see some text}', ['foo' => 'foo', 'bar' => 'bar'], false, true]; + yield ['Some message {foo}', ['foo' => 'foo', 'bar' => 'bar'], true, false]; + yield ['Some message {foo}', ['bar' => 'bar'], false, true]; + } + + private function mockTwigEnvironment() + { + $twigEnvironment = $this->createMock(Environment::class); + + $twigEnvironment->expects($this->any())->method('getCharset')->willReturn('UTF-8'); + + return $twigEnvironment; + } +} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php b/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php index bd7d57de02432..2f35c3bea36a1 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php @@ -87,13 +87,19 @@ public function dumpLog(Environment $env, string $message, Data $context = null) $message = twig_escape_filter($env, $message); $message = preg_replace('/"(.*?)"/', '"$1"', $message); - if (null === $context || !str_contains($message, '{')) { + $replacements = []; + foreach ($context ?? [] as $k => $v) { + $k = '{'.twig_escape_filter($env, $k).'}'; + if (str_contains($message, $k)) { + $replacements[$k] = $v; + } + } + + if (!$replacements) { return ''.$message.''; } - $replacements = []; - foreach ($context as $k => $v) { - $k = '{'.twig_escape_filter($env, $k).'}'; + foreach ($replacements as $k => $v) { $replacements['"'.$k.'"'] = $replacements['"'.$k.'"'] = $replacements[$k] = $this->dumpData($env, $v); } diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php index 01128eb150714..a004709ba504d 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php @@ -65,7 +65,7 @@ public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInter 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') && ($redis instanceof \Redis || $redis instanceof \RedisArray || $redis instanceof \RedisCluster)) { + if (\defined('Redis::OPT_COMPRESSION') && \in_array($redis::class, [\Redis::class, \RedisArray::class, \RedisCluster::class], true)) { $compression = $redis->getOption(\Redis::OPT_COMPRESSION); foreach (\is_array($compression) ? $compression : [$compression] as $c) { diff --git a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php index 67b212e520243..27fabf700af8a 100644 --- a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php +++ b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php @@ -60,6 +60,7 @@ public function testRedis6Proxy($class, $stub) $stub = file_get_contents("https://raw.githubusercontent.com/phpredis/phpredis/develop/{$stub}.stub.php"); $stub = preg_replace('/^class /m', 'return; \0', $stub); $stub = preg_replace('/^return; class ([a-zA-Z]++)/m', 'interface \1StubInterface', $stub, 1); + $stub = preg_replace('/^ public const .*/m', '', $stub); eval(substr($stub, 5)); $proxy = file_get_contents(\dirname(__DIR__, 2)."/Traits/{$class}6Proxy.php"); diff --git a/src/Symfony/Component/Cache/Traits/Redis6Proxy.php b/src/Symfony/Component/Cache/Traits/Redis6Proxy.php index f5373df72f3bf..8d9ac740fd52c 100644 --- a/src/Symfony/Component/Cache/Traits/Redis6Proxy.php +++ b/src/Symfony/Component/Cache/Traits/Redis6Proxy.php @@ -44,9 +44,9 @@ public function _compress($value): string return $this->lazyObjectReal->_compress(...\func_get_args()); } - public function _pack($value): string + public function _uncompress($value): string { - return $this->lazyObjectReal->_pack(...\func_get_args()); + return $this->lazyObjectReal->_uncompress(...\func_get_args()); } public function _prefix($key): string @@ -59,19 +59,19 @@ public function _serialize($value): string return $this->lazyObjectReal->_serialize(...\func_get_args()); } - public function _uncompress($value): string + public function _unserialize($value): mixed { - return $this->lazyObjectReal->_uncompress(...\func_get_args()); + return $this->lazyObjectReal->_unserialize(...\func_get_args()); } - public function _unpack($value): mixed + public function _pack($value): string { - return $this->lazyObjectReal->_unpack(...\func_get_args()); + return $this->lazyObjectReal->_pack(...\func_get_args()); } - public function _unserialize($value): mixed + public function _unpack($value): mixed { - return $this->lazyObjectReal->_unserialize(...\func_get_args()); + return $this->lazyObjectReal->_unpack(...\func_get_args()); } public function acl($subcmd, ...$args): mixed @@ -114,12 +114,12 @@ public function bitpos($key, $bit, $start = 0, $end = -1, $bybit = false): \Redi return $this->lazyObjectReal->bitpos(...\func_get_args()); } - public function blPop($key, $timeout_or_key, ...$extra_args): \Redis|array|false|null + public function blPop($key_or_keys, $timeout_or_key, ...$extra_args): \Redis|array|false|null { return $this->lazyObjectReal->blPop(...\func_get_args()); } - public function brPop($key, $timeout_or_key, ...$extra_args): \Redis|array|false|null + public function brPop($key_or_keys, $timeout_or_key, ...$extra_args): \Redis|array|false|null { return $this->lazyObjectReal->brPop(...\func_get_args()); } @@ -159,7 +159,7 @@ public function lmpop($keys, $from, $count = 1): \Redis|array|false|null return $this->lazyObjectReal->lmpop(...\func_get_args()); } - public function clearLastError(): \Redis|bool + public function clearLastError(): bool { return $this->lazyObjectReal->clearLastError(...\func_get_args()); } @@ -174,7 +174,7 @@ public function close(): bool return $this->lazyObjectReal->close(...\func_get_args()); } - public function command($opt, $arg): mixed + public function command($opt = null, ...$args): mixed { return $this->lazyObjectReal->command(...\func_get_args()); } @@ -194,7 +194,7 @@ public function copy($src, $dst, $options = null): \Redis|bool return $this->lazyObjectReal->copy(...\func_get_args()); } - public function dbSize(): \Redis|int + public function dbSize(): \Redis|false|int { return $this->lazyObjectReal->dbSize(...\func_get_args()); } @@ -304,7 +304,7 @@ public function flushDB($sync = null): \Redis|bool return $this->lazyObjectReal->flushDB(...\func_get_args()); } - public function geoadd($key, $lng, $lat, $member, ...$other_triples): \Redis|false|int + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \Redis|false|int { return $this->lazyObjectReal->geoadd(...\func_get_args()); } @@ -424,7 +424,7 @@ public function lcs($key1, $key2, $options = null): \Redis|array|false|int|strin return $this->lazyObjectReal->lcs(...\func_get_args()); } - public function getReadTimeout(): int + public function getReadTimeout(): float { return $this->lazyObjectReal->getReadTimeout(...\func_get_args()); } @@ -434,17 +434,27 @@ public function getset($key, $value): \Redis|false|string return $this->lazyObjectReal->getset(...\func_get_args()); } - public function getTimeout(): int + public function getTimeout(): false|float { return $this->lazyObjectReal->getTimeout(...\func_get_args()); } - public function hDel($key, $member, ...$other_members): \Redis|false|int + public function getTransferredBytes(): array + { + return $this->lazyObjectReal->getTransferredBytes(...\func_get_args()); + } + + public function clearTransferredBytes(): void + { + $this->lazyObjectReal->clearTransferredBytes(...\func_get_args()); + } + + public function hDel($key, $field, ...$other_fields): \Redis|false|int { return $this->lazyObjectReal->hDel(...\func_get_args()); } - public function hExists($key, $member): \Redis|bool + public function hExists($key, $field): \Redis|bool { return $this->lazyObjectReal->hExists(...\func_get_args()); } @@ -459,12 +469,12 @@ public function hGetAll($key): \Redis|array|false return $this->lazyObjectReal->hGetAll(...\func_get_args()); } - public function hIncrBy($key, $member, $value): \Redis|false|int + public function hIncrBy($key, $field, $value): \Redis|false|int { return $this->lazyObjectReal->hIncrBy(...\func_get_args()); } - public function hIncrByFloat($key, $member, $value): \Redis|false|float + public function hIncrByFloat($key, $field, $value): \Redis|false|float { return $this->lazyObjectReal->hIncrByFloat(...\func_get_args()); } @@ -479,12 +489,12 @@ public function hLen($key): \Redis|false|int return $this->lazyObjectReal->hLen(...\func_get_args()); } - public function hMget($key, $keys): \Redis|array|false + public function hMget($key, $fields): \Redis|array|false { return $this->lazyObjectReal->hMget(...\func_get_args()); } - public function hMset($key, $keyvals): \Redis|bool + public function hMset($key, $fieldvals): \Redis|bool { return $this->lazyObjectReal->hMset(...\func_get_args()); } @@ -499,12 +509,12 @@ public function hSet($key, $member, $value): \Redis|false|int return $this->lazyObjectReal->hSet(...\func_get_args()); } - public function hSetNx($key, $member, $value): \Redis|bool + public function hSetNx($key, $field, $value): \Redis|bool { return $this->lazyObjectReal->hSetNx(...\func_get_args()); } - public function hStrLen($key, $member): \Redis|false|int + public function hStrLen($key, $field): \Redis|false|int { return $this->lazyObjectReal->hStrLen(...\func_get_args()); } @@ -519,17 +529,17 @@ public function hscan($key, &$iterator, $pattern = null, $count = 0): \Redis|arr return $this->lazyObjectReal->hscan(...\func_get_args()); } - public function incr($key, $by = 1) + public function incr($key, $by = 1): \Redis|false|int { return $this->lazyObjectReal->incr(...\func_get_args()); } - public function incrBy($key, $value) + public function incrBy($key, $value): \Redis|false|int { return $this->lazyObjectReal->incrBy(...\func_get_args()); } - public function incrByFloat($key, $value) + public function incrByFloat($key, $value): \Redis|false|float { return $this->lazyObjectReal->incrByFloat(...\func_get_args()); } @@ -564,6 +574,11 @@ public function lMove($src, $dst, $wherefrom, $whereto): \Redis|false|string return $this->lazyObjectReal->lMove(...\func_get_args()); } + public function blmove($src, $dst, $wherefrom, $whereto, $timeout): \Redis|false|string + { + return $this->lazyObjectReal->blmove(...\func_get_args()); + } + public function lPop($key, $count = 0): \Redis|array|bool|string { return $this->lazyObjectReal->lPop(...\func_get_args()); @@ -574,22 +589,22 @@ public function lPos($key, $value, $options = null): \Redis|array|bool|int|null return $this->lazyObjectReal->lPos(...\func_get_args()); } - public function lPush($key, ...$elements) + public function lPush($key, ...$elements): \Redis|false|int { return $this->lazyObjectReal->lPush(...\func_get_args()); } - public function rPush($key, ...$elements) + public function rPush($key, ...$elements): \Redis|false|int { return $this->lazyObjectReal->rPush(...\func_get_args()); } - public function lPushx($key, $value) + public function lPushx($key, $value): \Redis|false|int { return $this->lazyObjectReal->lPushx(...\func_get_args()); } - public function rPushx($key, $value) + public function rPushx($key, $value): \Redis|false|int { return $this->lazyObjectReal->rPushx(...\func_get_args()); } @@ -614,7 +629,7 @@ public function lrange($key, $start, $end): \Redis|array|false return $this->lazyObjectReal->lrange(...\func_get_args()); } - public function lrem($key, $value, $count = 0) + public function lrem($key, $value, $count = 0): \Redis|false|int { return $this->lazyObjectReal->lrem(...\func_get_args()); } @@ -624,7 +639,7 @@ public function ltrim($key, $start, $end): \Redis|bool return $this->lazyObjectReal->ltrim(...\func_get_args()); } - public function mget($keys) + public function mget($keys): \Redis|array { return $this->lazyObjectReal->mget(...\func_get_args()); } @@ -634,7 +649,7 @@ public function migrate($host, $port, $key, $dstdb, $timeout, $copy = false, $re return $this->lazyObjectReal->migrate(...\func_get_args()); } - public function move($key, $index): bool + public function move($key, $index): \Redis|bool { return $this->lazyObjectReal->move(...\func_get_args()); } @@ -669,7 +684,7 @@ public function pconnect($host, $port = 6379, $timeout = 0.0, $persistent_id = n return $this->lazyObjectReal->pconnect(...\func_get_args()); } - public function persist($key): bool + public function persist($key): \Redis|bool { return $this->lazyObjectReal->persist(...\func_get_args()); } @@ -679,27 +694,27 @@ public function pexpire($key, $timeout, $mode = null): bool return $this->lazyObjectReal->pexpire(...\func_get_args()); } - public function pexpireAt($key, $timestamp, $mode = null): bool + public function pexpireAt($key, $timestamp, $mode = null): \Redis|bool { return $this->lazyObjectReal->pexpireAt(...\func_get_args()); } - public function pfadd($key, $elements): int + public function pfadd($key, $elements): \Redis|int { return $this->lazyObjectReal->pfadd(...\func_get_args()); } - public function pfcount($key): int + public function pfcount($key): \Redis|int { return $this->lazyObjectReal->pfcount(...\func_get_args()); } - public function pfmerge($dst, $keys): bool + public function pfmerge($dst, $srckeys): \Redis|bool { return $this->lazyObjectReal->pfmerge(...\func_get_args()); } - public function ping($key = null) + public function ping($message = null): \Redis|bool|string { return $this->lazyObjectReal->ping(...\func_get_args()); } @@ -714,7 +729,7 @@ public function popen($host, $port = 6379, $timeout = 0.0, $persistent_id = null return $this->lazyObjectReal->popen(...\func_get_args()); } - public function psetex($key, $expire, $value) + public function psetex($key, $expire, $value): \Redis|bool { return $this->lazyObjectReal->psetex(...\func_get_args()); } @@ -729,7 +744,7 @@ public function pttl($key): \Redis|false|int return $this->lazyObjectReal->pttl(...\func_get_args()); } - public function publish($channel, $message): mixed + public function publish($channel, $message): \Redis|false|int { return $this->lazyObjectReal->publish(...\func_get_args()); } @@ -749,7 +764,7 @@ public function rPop($key, $count = 0): \Redis|array|bool|string return $this->lazyObjectReal->rPop(...\func_get_args()); } - public function randomKey() + public function randomKey(): \Redis|false|string { return $this->lazyObjectReal->randomKey(...\func_get_args()); } @@ -759,17 +774,17 @@ public function rawcommand($command, ...$args): mixed return $this->lazyObjectReal->rawcommand(...\func_get_args()); } - public function rename($key_src, $key_dst) + public function rename($old_name, $new_name): \Redis|bool { return $this->lazyObjectReal->rename(...\func_get_args()); } - public function renameNx($key_src, $key_dst) + public function renameNx($key_src, $key_dst): \Redis|bool { return $this->lazyObjectReal->renameNx(...\func_get_args()); } - public function restore($key, $timeout, $value, $options = null): bool + public function restore($key, $ttl, $value, $options = null): \Redis|bool { return $this->lazyObjectReal->restore(...\func_get_args()); } @@ -779,7 +794,7 @@ public function role(): mixed return $this->lazyObjectReal->role(...\func_get_args()); } - public function rpoplpush($src, $dst): \Redis|false|string + public function rpoplpush($srckey, $dstkey): \Redis|false|string { return $this->lazyObjectReal->rpoplpush(...\func_get_args()); } @@ -824,7 +839,7 @@ public function sMembers($key): \Redis|array|false return $this->lazyObjectReal->sMembers(...\func_get_args()); } - public function sMisMember($key, $member, ...$other_members): array + public function sMisMember($key, $member, ...$other_members): \Redis|array|false { return $this->lazyObjectReal->sMisMember(...\func_get_args()); } @@ -854,7 +869,7 @@ public function sUnionStore($dst, $key, ...$other_keys): \Redis|false|int return $this->lazyObjectReal->sUnionStore(...\func_get_args()); } - public function save(): bool + public function save(): \Redis|bool { return $this->lazyObjectReal->save(...\func_get_args()); } @@ -879,17 +894,17 @@ public function select($db): \Redis|bool return $this->lazyObjectReal->select(...\func_get_args()); } - public function set($key, $value, $opt = null): \Redis|bool|string + public function set($key, $value, $options = null): \Redis|bool|string { return $this->lazyObjectReal->set(...\func_get_args()); } - public function setBit($key, $idx, $value) + public function setBit($key, $idx, $value): \Redis|false|int { return $this->lazyObjectReal->setBit(...\func_get_args()); } - public function setRange($key, $start, $value) + public function setRange($key, $index, $value): \Redis|false|int { return $this->lazyObjectReal->setRange(...\func_get_args()); } @@ -904,7 +919,7 @@ public function setex($key, $expire, $value) return $this->lazyObjectReal->setex(...\func_get_args()); } - public function setnx($key, $value) + public function setnx($key, $value): \Redis|bool { return $this->lazyObjectReal->setnx(...\func_get_args()); } @@ -914,11 +929,21 @@ public function sismember($key, $value): \Redis|bool return $this->lazyObjectReal->sismember(...\func_get_args()); } - public function slaveof($host = null, $port = 6379): bool + public function slaveof($host = null, $port = 6379): \Redis|bool { return $this->lazyObjectReal->slaveof(...\func_get_args()); } + public function replicaof($host = null, $port = 6379): \Redis|bool + { + return $this->lazyObjectReal->replicaof(...\func_get_args()); + } + + public function touch($key_or_array, ...$more_keys): \Redis|false|int + { + return $this->lazyObjectReal->touch(...\func_get_args()); + } + public function slowlog($operation, $length = 0): mixed { return $this->lazyObjectReal->slowlog(...\func_get_args()); @@ -929,6 +954,11 @@ public function sort($key, $options = null): mixed return $this->lazyObjectReal->sort(...\func_get_args()); } + public function sort_ro($key, $options = null): mixed + { + return $this->lazyObjectReal->sort_ro(...\func_get_args()); + } + public function sortAsc($key, $pattern = null, $get = null, $offset = -1, $count = -1, $store = null): array { return $this->lazyObjectReal->sortAsc(...\func_get_args()); @@ -959,7 +989,12 @@ public function sscan($key, &$iterator, $pattern = null, $count = 0): array|fals return $this->lazyObjectReal->sscan(...\func_get_args()); } - public function strlen($key) + public function ssubscribe($channels, $cb): bool + { + return $this->lazyObjectReal->ssubscribe(...\func_get_args()); + } + + public function strlen($key): \Redis|false|int { return $this->lazyObjectReal->strlen(...\func_get_args()); } @@ -969,12 +1004,17 @@ public function subscribe($channels, $cb): bool return $this->lazyObjectReal->subscribe(...\func_get_args()); } - public function swapdb($src, $dst): bool + public function sunsubscribe($channels): \Redis|array|bool + { + return $this->lazyObjectReal->sunsubscribe(...\func_get_args()); + } + + public function swapdb($src, $dst): \Redis|bool { return $this->lazyObjectReal->swapdb(...\func_get_args()); } - public function time(): array + public function time(): \Redis|array { return $this->lazyObjectReal->time(...\func_get_args()); } @@ -984,12 +1024,12 @@ public function ttl($key): \Redis|false|int return $this->lazyObjectReal->ttl(...\func_get_args()); } - public function type($key) + public function type($key): \Redis|false|int { return $this->lazyObjectReal->type(...\func_get_args()); } - public function unlink($key, ...$other_keys) + public function unlink($key, ...$other_keys): \Redis|false|int { return $this->lazyObjectReal->unlink(...\func_get_args()); } @@ -999,17 +1039,17 @@ public function unsubscribe($channels): \Redis|array|bool return $this->lazyObjectReal->unsubscribe(...\func_get_args()); } - public function unwatch() + public function unwatch(): \Redis|bool { return $this->lazyObjectReal->unwatch(...\func_get_args()); } - public function watch($key, ...$other_keys) + public function watch($key, ...$other_keys): \Redis|bool { return $this->lazyObjectReal->watch(...\func_get_args()); } - public function wait($count, $timeout): false|int + public function wait($numreplicas, $timeout): false|int { return $this->lazyObjectReal->wait(...\func_get_args()); } @@ -1039,7 +1079,7 @@ public function xdel($key, $ids): \Redis|false|int return $this->lazyObjectReal->xdel(...\func_get_args()); } - public function xgroup($operation, $key = null, $arg1 = null, $arg2 = null, $arg3 = false): mixed + 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()); } @@ -1074,12 +1114,12 @@ public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): return $this->lazyObjectReal->xreadgroup(...\func_get_args()); } - public function xrevrange($key, $start, $end, $count = -1): \Redis|array|bool + public function xrevrange($key, $end, $start, $count = -1): \Redis|array|bool { return $this->lazyObjectReal->xrevrange(...\func_get_args()); } - public function xtrim($key, $maxlen, $approx = false, $minid = false, $limit = -1): \Redis|false|int + public function xtrim($key, $threshold, $approx = false, $minid = false, $limit = -1): \Redis|false|int { return $this->lazyObjectReal->xtrim(...\func_get_args()); } @@ -1114,12 +1154,12 @@ public function zMscore($key, $member, ...$other_members): \Redis|array|false return $this->lazyObjectReal->zMscore(...\func_get_args()); } - public function zPopMax($key, $value = null): \Redis|array|false + public function zPopMax($key, $count = null): \Redis|array|false { return $this->lazyObjectReal->zPopMax(...\func_get_args()); } - public function zPopMin($key, $value = null): \Redis|array|false + public function zPopMin($key, $count = null): \Redis|array|false { return $this->lazyObjectReal->zPopMin(...\func_get_args()); } @@ -1179,12 +1219,12 @@ public function zRevRange($key, $start, $end, $scores = null): \Redis|array|fals return $this->lazyObjectReal->zRevRange(...\func_get_args()); } - public function zRevRangeByLex($key, $min, $max, $offset = -1, $count = -1): \Redis|array|false + public function zRevRangeByLex($key, $max, $min, $offset = -1, $count = -1): \Redis|array|false { return $this->lazyObjectReal->zRevRangeByLex(...\func_get_args()); } - public function zRevRangeByScore($key, $start, $end, $options = []): \Redis|array|false + public function zRevRangeByScore($key, $max, $min, $options = []): \Redis|array|false { return $this->lazyObjectReal->zRevRangeByScore(...\func_get_args()); } @@ -1204,7 +1244,7 @@ public function zdiff($keys, $options = null): \Redis|array|false return $this->lazyObjectReal->zdiff(...\func_get_args()); } - public function zdiffstore($dst, $keys, $options = null): \Redis|false|int + public function zdiffstore($dst, $keys): \Redis|false|int { return $this->lazyObjectReal->zdiffstore(...\func_get_args()); } @@ -1224,7 +1264,7 @@ public function zinterstore($dst, $keys, $weights = null, $aggregate = null): \R return $this->lazyObjectReal->zinterstore(...\func_get_args()); } - public function zscan($key, &$iterator, $pattern = null, $count = 0): \Redis|array|bool + public function zscan($key, &$iterator, $pattern = null, $count = 0): \Redis|array|false { return $this->lazyObjectReal->zscan(...\func_get_args()); } diff --git a/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php b/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php index 9cc6abf7fad94..e94fa2b36711e 100644 --- a/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php +++ b/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php @@ -44,44 +44,44 @@ public function _compress($value): string return $this->lazyObjectReal->_compress(...\func_get_args()); } - public function _masters(): array + public function _uncompress($value): string { - return $this->lazyObjectReal->_masters(...\func_get_args()); + return $this->lazyObjectReal->_uncompress(...\func_get_args()); } - public function _pack($value): string + public function _serialize($value): bool|string { - return $this->lazyObjectReal->_pack(...\func_get_args()); + return $this->lazyObjectReal->_serialize(...\func_get_args()); } - public function _prefix($key): bool|string + public function _unserialize($value): mixed { - return $this->lazyObjectReal->_prefix(...\func_get_args()); + return $this->lazyObjectReal->_unserialize(...\func_get_args()); } - public function _redir(): ?string + public function _pack($value): string { - return $this->lazyObjectReal->_redir(...\func_get_args()); + return $this->lazyObjectReal->_pack(...\func_get_args()); } - public function _serialize($value): bool|string + public function _unpack($value): mixed { - return $this->lazyObjectReal->_serialize(...\func_get_args()); + return $this->lazyObjectReal->_unpack(...\func_get_args()); } - public function _uncompress($value): string + public function _prefix($key): bool|string { - return $this->lazyObjectReal->_uncompress(...\func_get_args()); + return $this->lazyObjectReal->_prefix(...\func_get_args()); } - public function _unpack($value): mixed + public function _masters(): array { - return $this->lazyObjectReal->_unpack(...\func_get_args()); + return $this->lazyObjectReal->_masters(...\func_get_args()); } - public function _unserialize($value): mixed + public function _redir(): ?string { - return $this->lazyObjectReal->_unserialize(...\func_get_args()); + return $this->lazyObjectReal->_redir(...\func_get_args()); } public function acl($key_or_address, $subcmd, ...$args): mixed @@ -134,6 +134,16 @@ public function brpoplpush($srckey, $deskey, $timeout): mixed return $this->lazyObjectReal->brpoplpush(...\func_get_args()); } + public function lmove($src, $dst, $wherefrom, $whereto): \Redis|false|string + { + return $this->lazyObjectReal->lmove(...\func_get_args()); + } + + public function blmove($src, $dst, $wherefrom, $whereto, $timeout): \Redis|false|string + { + return $this->lazyObjectReal->blmove(...\func_get_args()); + } + public function bzpopmax($key, $timeout_or_key, ...$extra_args): array { return $this->lazyObjectReal->bzpopmax(...\func_get_args()); @@ -199,6 +209,11 @@ public function dbsize($key_or_address): \RedisCluster|int return $this->lazyObjectReal->dbsize(...\func_get_args()); } + public function copy($src, $dst, $options = null): \RedisCluster|bool + { + return $this->lazyObjectReal->copy(...\func_get_args()); + } + public function decr($key, $by = 1): \RedisCluster|false|int { return $this->lazyObjectReal->decr(...\func_get_args()); @@ -264,6 +279,11 @@ public function exists($key, ...$other_keys): \RedisCluster|bool|int return $this->lazyObjectReal->exists(...\func_get_args()); } + public function touch($key, ...$other_keys): \RedisCluster|bool|int + { + return $this->lazyObjectReal->touch(...\func_get_args()); + } + public function expire($key, $timeout, $mode = null): \RedisCluster|bool { return $this->lazyObjectReal->expire(...\func_get_args()); @@ -294,7 +314,7 @@ public function flushdb($key_or_address, $async = false): \RedisCluster|bool return $this->lazyObjectReal->flushdb(...\func_get_args()); } - public function geoadd($key, $lng, $lat, $member, ...$other_triples): \RedisCluster|int + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \RedisCluster|false|int { return $this->lazyObjectReal->geoadd(...\func_get_args()); } @@ -334,6 +354,16 @@ public function georadiusbymember_ro($key, $member, $radius, $unit, $options = [ return $this->lazyObjectReal->georadiusbymember_ro(...\func_get_args()); } + public function geosearch($key, $position, $shape, $unit, $options = []): \RedisCluster|array + { + return $this->lazyObjectReal->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \RedisCluster|array|false|int + { + return $this->lazyObjectReal->geosearchstore(...\func_get_args()); + } + public function get($key): mixed { return $this->lazyObjectReal->get(...\func_get_args()); @@ -374,6 +404,16 @@ public function getset($key, $value): \RedisCluster|bool|string return $this->lazyObjectReal->getset(...\func_get_args()); } + public function gettransferredbytes(): array|false + { + return $this->lazyObjectReal->gettransferredbytes(...\func_get_args()); + } + + public function cleartransferredbytes(): void + { + $this->lazyObjectReal->cleartransferredbytes(...\func_get_args()); + } + public function hdel($key, $member, ...$other_members): \RedisCluster|false|int { return $this->lazyObjectReal->hdel(...\func_get_args()); @@ -429,6 +469,11 @@ public function hscan($key, &$iterator, $pattern = null, $count = 0): array|bool return $this->lazyObjectReal->hscan(...\func_get_args()); } + public function hrandfield($key, $options = null): \RedisCluster|array|string + { + return $this->lazyObjectReal->hrandfield(...\func_get_args()); + } + public function hset($key, $member, $value): \RedisCluster|false|int { return $this->lazyObjectReal->hset(...\func_get_args()); @@ -504,6 +549,11 @@ public function lpop($key, $count = 0): \RedisCluster|array|bool|string return $this->lazyObjectReal->lpop(...\func_get_args()); } + public function lpos($key, $value, $options = null): \Redis|array|bool|int|null + { + return $this->lazyObjectReal->lpos(...\func_get_args()); + } + public function lpush($key, $value, ...$other_values): \RedisCluster|bool|int { return $this->lazyObjectReal->lpush(...\func_get_args()); @@ -764,6 +814,11 @@ public function sismember($key, $value): \RedisCluster|bool return $this->lazyObjectReal->sismember(...\func_get_args()); } + public function smismember($key, $member, ...$other_members): \RedisCluster|array|false + { + return $this->lazyObjectReal->smismember(...\func_get_args()); + } + public function slowlog($key_or_address, ...$args): mixed { return $this->lazyObjectReal->slowlog(...\func_get_args()); @@ -784,6 +839,11 @@ public function sort($key, $options = null): \RedisCluster|array|bool|int|string return $this->lazyObjectReal->sort(...\func_get_args()); } + public function sort_ro($key, $options = null): \RedisCluster|array|bool|int|string + { + return $this->lazyObjectReal->sort_ro(...\func_get_args()); + } + public function spop($key, $count = 0): \RedisCluster|array|false|string { return $this->lazyObjectReal->spop(...\func_get_args()); @@ -879,11 +939,16 @@ public function xdel($key, $ids): \RedisCluster|false|int return $this->lazyObjectReal->xdel(...\func_get_args()); } - public function xgroup($operation, $key = null, $arg1 = null, $arg2 = null, $arg3 = false): mixed + 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 xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \RedisCluster|array|bool + { + return $this->lazyObjectReal->xautoclaim(...\func_get_args()); + } + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed { return $this->lazyObjectReal->xinfo(...\func_get_args()); @@ -979,6 +1044,11 @@ public function zrangestore($dstkey, $srckey, $start, $end, $options = null): \R return $this->lazyObjectReal->zrangestore(...\func_get_args()); } + public function zrandmember($key, $options = null): \RedisCluster|array|string + { + return $this->lazyObjectReal->zrandmember(...\func_get_args()); + } + public function zrangebylex($key, $min, $max, $offset = -1, $count = -1): \RedisCluster|array|false { return $this->lazyObjectReal->zrangebylex(...\func_get_args()); @@ -1044,8 +1114,33 @@ public function zscore($key, $member): \RedisCluster|false|float return $this->lazyObjectReal->zscore(...\func_get_args()); } + public function zmscore($key, $member, ...$other_members): \Redis|array|false + { + return $this->lazyObjectReal->zmscore(...\func_get_args()); + } + public function zunionstore($dst, $keys, $weights = null, $aggregate = null): \RedisCluster|false|int { return $this->lazyObjectReal->zunionstore(...\func_get_args()); } + + public function zinter($keys, $weights = null, $options = null): \RedisCluster|array|false + { + return $this->lazyObjectReal->zinter(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \RedisCluster|false|int + { + return $this->lazyObjectReal->zdiffstore(...\func_get_args()); + } + + public function zunion($keys, $weights = null, $options = null): \RedisCluster|array|false + { + return $this->lazyObjectReal->zunion(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \RedisCluster|array|false + { + return $this->lazyObjectReal->zdiff(...\func_get_args()); + } } diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index ccbcb0f9a5cdf..f791e82343f3a 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -550,7 +550,11 @@ 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; }, $results); + $results = array_map(function ($v) use ($e) { return false === $v ? $e : $v; }, (array) $results); + } + + if (\is_bool($results)) { + return; } foreach ($ids as $k => $id) { diff --git a/src/Symfony/Component/Clock/MonotonicClock.php b/src/Symfony/Component/Clock/MonotonicClock.php index ef4bfd50cb697..badb99f1daba0 100644 --- a/src/Symfony/Component/Clock/MonotonicClock.php +++ b/src/Symfony/Component/Clock/MonotonicClock.php @@ -67,7 +67,7 @@ public function sleep(float|int $seconds): void } if (0 < $us = $seconds - $s) { - usleep($us * 1E6); + usleep((int) ($us * 1E6)); } } diff --git a/src/Symfony/Component/Clock/NativeClock.php b/src/Symfony/Component/Clock/NativeClock.php index 0fcf7879a2d9d..21ade183fb435 100644 --- a/src/Symfony/Component/Clock/NativeClock.php +++ b/src/Symfony/Component/Clock/NativeClock.php @@ -41,7 +41,7 @@ public function sleep(float|int $seconds): void } if (0 < $us = $seconds - $s) { - usleep($us * 1E6); + usleep((int) ($us * 1E6)); } } diff --git a/src/Symfony/Component/Clock/Tests/MonotonicClockTest.php b/src/Symfony/Component/Clock/Tests/MonotonicClockTest.php index 3230c75371b2e..54cfe78c375cc 100644 --- a/src/Symfony/Component/Clock/Tests/MonotonicClockTest.php +++ b/src/Symfony/Component/Clock/Tests/MonotonicClockTest.php @@ -56,7 +56,7 @@ public function testSleep() usleep(10); $after = microtime(true); - $this->assertGreaterThanOrEqual($before + 1.5, $now); + $this->assertGreaterThanOrEqual($before + 1.499999, $now); $this->assertLessThan($after, $now); $this->assertLessThan(1.9, $now - $before); $this->assertSame($tz, $clock->now()->getTimezone()->getName()); diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 99548faf858b8..d4ec1be090927 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -298,6 +298,8 @@ public function doRun(InputInterface $input, OutputInterface $output) return isset($event) ? $event->getExitCode() : 1; } + + throw $e; } catch (NamespaceNotFoundException) { throw $e; } diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index c178151688928..c82c3eb188418 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Console\Command\HelpCommand; use Symfony\Component\Console\Command\LazyCommand; use Symfony\Component\Console\Command\SignalableCommandInterface; +use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; use Symfony\Component\Console\CommandLoader\FactoryCommandLoader; use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; use Symfony\Component\Console\Event\ConsoleCommandEvent; @@ -1466,6 +1467,25 @@ public function testRunWithError() } } + public function testRunWithFindError() + { + $this->expectException(\Error::class); + $this->expectExceptionMessage('Find exception'); + + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + // Throws an exception when find fails + $commandLoader = $this->createMock(CommandLoaderInterface::class); + $commandLoader->method('getNames')->willThrowException(new \Error('Find exception')); + $application->setCommandLoader($commandLoader); + + // The exception should not be ignored + $tester = new ApplicationTester($application); + $tester->run(['command' => 'foo']); + } + public function testRunAllowsErrorListenersToSilenceTheException() { $dispatcher = $this->getDispatcher(); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php index c629798bfca93..c8217154741c9 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php @@ -126,7 +126,7 @@ private function processDefinition(ContainerBuilder $container, string $id, Defi foreach ($instanceofTags[$i] as $k => $v) { if (null === $definition->getDecoratedService() || \in_array($k, $tagsToKeep, true)) { foreach ($v as $v) { - if ($definition->hasTag($k) && (!$v || \in_array($v, $definition->getTag($k)))) { + if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k))) { continue; } $definition->addTag($k, $v); diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 049b611ea73b3..ae7ca22ee5035 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -506,8 +506,8 @@ public function has(string $id): bool */ public function get(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): ?object { - if ($this->isCompiled() && isset($this->removedIds[$id]) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior) { - return parent::get($id); + if ($this->isCompiled() && isset($this->removedIds[$id])) { + return ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior ? parent::get($id) : null; } return $this->doGet($id, $invalidBehavior); @@ -524,9 +524,9 @@ private function doGet(string $id, int $invalidBehavior = ContainerInterface::EX } try { if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) { - return parent::get($id, $invalidBehavior); + return $this->privates[$id] ?? parent::get($id, $invalidBehavior); } - if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { + if (null !== $service = $this->privates[$id] ?? parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { return $service; } } catch (ServiceCircularReferenceException $e) { @@ -1029,8 +1029,8 @@ private function createService(Definition $definition, array &$inlineServices, b $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($arguments)), $inlineServices, $isConstructorArgument); - if (null !== $id && $definition->isShared() && isset($this->services[$id]) && (true === $tryProxy || !$definition->isLazy())) { - return $this->services[$id]; + if (null !== $id && $definition->isShared() && (isset($this->services[$id]) || isset($this->privates[$id])) && (true === $tryProxy || !$definition->isLazy())) { + return $this->services[$id] ?? $this->privates[$id]; } if (null !== $factory) { @@ -1587,7 +1587,11 @@ private function shareService(Definition $definition, mixed $service, ?string $i $inlineServices[$id ?? spl_object_hash($definition)] = $service; if (null !== $id && $definition->isShared()) { - $this->services[$id] = $service; + if ($definition->isPrivate() && $this->isCompiled()) { + $this->privates[$id] = $service; + } else { + $this->services[$id] = $service; + } unset($this->loading[$id]); } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 4d827d53e28b7..e930724810ae9 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -545,10 +545,10 @@ private function generateProxyClasses(): array if (!$definition = $this->isProxyCandidate($definition, $asGhostObject, $id)) { continue; } - if (isset($alreadyGenerated[$class = $definition->getClass()])) { + if (isset($alreadyGenerated[$asGhostObject][$class = $definition->getClass()])) { continue; } - $alreadyGenerated[$class] = true; + $alreadyGenerated[$asGhostObject][$class] = true; // register class' reflector for resource tracking $this->container->getReflectionClass($class); if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition, $id)) { diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/LazyServiceInstantiator.php b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/LazyServiceInstantiator.php index cb846445de994..3de3d0361d775 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/LazyServiceInstantiator.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/LazyServiceInstantiator.php @@ -13,8 +13,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper; -use Symfony\Component\VarExporter\LazyGhostTrait; /** * @author Nicolas Grekas @@ -25,10 +25,14 @@ public function instantiateProxy(ContainerInterface $container, Definition $defi { $dumper = new LazyServiceDumper(); - if (!class_exists($proxyClass = $dumper->getProxyClass($definition, $class), false)) { + if (!$dumper->isProxyCandidate($definition, $asGhostObject, $id)) { + throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service "%s".', $id)); + } + + if (!class_exists($proxyClass = $dumper->getProxyClass($definition, $asGhostObject, $class), false)) { eval($dumper->getProxyCode($definition, $id)); } - return isset(class_uses($proxyClass)[LazyGhostTrait::class]) ? $proxyClass::createLazyGhost($realInstantiator) : $proxyClass::createLazyProxy($realInstantiator); + return $asGhostObject ? $proxyClass::createLazyGhost($realInstantiator) : $proxyClass::createLazyProxy($realInstantiator); } } diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php index 8f69da33929e9..eeef902b0f889 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/LazyServiceDumper.php @@ -72,9 +72,10 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ $instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); } - $proxyClass = $this->getProxyClass($definition); + $asGhostObject = str_contains($factoryCode, '$proxy'); + $proxyClass = $this->getProxyClass($definition, $asGhostObject); - if (!str_contains($factoryCode, '$proxy')) { + if (!$asGhostObject) { return <<createProxy('$proxyClass', fn () => \\$proxyClass::createLazyProxy(fn () => $factoryCode)); @@ -104,7 +105,7 @@ public function getProxyCode(Definition $definition, string $id = null): string if (!$this->isProxyCandidate($definition, $asGhostObject, $id)) { throw new InvalidArgumentException(sprintf('Cannot instantiate lazy proxy for service "%s".', $id ?? $definition->getClass())); } - $proxyClass = $this->getProxyClass($definition, $class); + $proxyClass = $this->getProxyClass($definition, $asGhostObject, $class); if ($asGhostObject) { try { @@ -142,10 +143,12 @@ public function getProxyCode(Definition $definition, string $id = null): string } } - public function getProxyClass(Definition $definition, \ReflectionClass &$class = null): string + public function getProxyClass(Definition $definition, bool $asGhostObject, \ReflectionClass &$class = null): string { $class = new \ReflectionClass($definition->getClass()); - return preg_replace('/^.*\\\\/', '', $class->name).'_'.substr(hash('sha256', $this->salt.'+'.$class->name), -7); + return preg_replace('/^.*\\\\/', '', $class->name) + .($asGhostObject ? 'Ghost' : 'Proxy') + .ucfirst(substr(hash('sha256', $this->salt.'+'.$class->name), -7)); } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 76e2d1b8555a3..ab76eb9e0df08 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -336,7 +336,7 @@ private function parseDefinition(\DOMElement $service, string $file, Definition $tags = $this->getChildren($service, 'tag'); foreach ($tags as $tag) { - if ('' === $tagName = $tag->hasChildNodes() || '' === $tag->nodeValue ? $tag->getAttribute('name') : $tag->nodeValue) { + if ('' === $tagName = $tag->childElementCount || '' === $tag->nodeValue ? $tag->getAttribute('name') : $tag->nodeValue) { throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', (string) $service->getAttribute('id'), $file)); } diff --git a/src/Symfony/Component/DependencyInjection/ReverseContainer.php b/src/Symfony/Component/DependencyInjection/ReverseContainer.php index 9635c5bceefe5..9b1d331d85e63 100644 --- a/src/Symfony/Component/DependencyInjection/ReverseContainer.php +++ b/src/Symfony/Component/DependencyInjection/ReverseContainer.php @@ -63,10 +63,6 @@ public function getId(object $service): ?string */ public function getService(string $id): object { - if ($this->serviceContainer->has($id)) { - return $this->serviceContainer->get($id); - } - if ($this->reversibleLocator->has($id)) { return $this->reversibleLocator->get($id); } @@ -75,7 +71,6 @@ public function getService(string $id): object throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is private and cannot be accessed by reference. You should either make it public, or tag it as "%s".', $id, $this->tagName)); } - // will throw a ServiceNotFoundException - $this->serviceContainer->get($id); + return $this->serviceContainer->get($id); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index 72f875db6903d..e7fcac4623f84 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -849,7 +849,6 @@ static function (ChildDefinition $definition, CustomAutoconfiguration $attribute $definition->addTag('app.custom_tag', get_object_vars($attribute) + ['class' => $reflector->getName()]); } ); - $container->registerForAutoconfiguration(TaggedService1::class)->addTag('app.custom_tag'); $container->register('one', TaggedService1::class) ->setPublic(true) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php index 4d248306a0b7d..6ddbc88d8ee8c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php @@ -184,19 +184,19 @@ public function testIndexedByServiceIdWithDecoration() $container->setDefinition(Service::class, $service); - $decorated = new Definition(Decorated::class); + $decorated = new Definition(DecoratedService::class); $decorated->setPublic(true); $decorated->setDecoratedService(Service::class); - $container->setDefinition(Decorated::class, $decorated); + $container->setDefinition(DecoratedService::class, $decorated); $container->compile(); /** @var ServiceLocator $locator */ $locator = $container->get(Locator::class)->locator; static::assertTrue($locator->has(Service::class)); - static::assertFalse($locator->has(Decorated::class)); - static::assertInstanceOf(Decorated::class, $locator->get(Service::class)); + static::assertFalse($locator->has(DecoratedService::class)); + static::assertInstanceOf(DecoratedService::class, $locator->get(Service::class)); } public function testDefinitionOrderIsTheSame() diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index bbf618b981e85..9ec5d0631fe61 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1127,10 +1127,19 @@ public function testPrivateServiceUser() $container->addDefinitions([ 'bar' => $fooDefinition, 'bar_user' => $fooUserDefinition->setPublic(true), + 'bar_user2' => $fooUserDefinition->setPublic(true), ]); $container->compile(); + $this->assertNull($container->get('bar', $container::NULL_ON_INVALID_REFERENCE)); $this->assertInstanceOf(\BarClass::class, $container->get('bar_user')->bar); + + // Ensure that accessing a public service with a shared private service + // does not make the private service available. + $this->assertNull($container->get('bar', $container::NULL_ON_INVALID_REFERENCE)); + + // Ensure the private service is still shared. + $this->assertSame($container->get('bar_user')->bar, $container->get('bar_user2')->bar); } public function testThrowsExceptionWhenSetServiceOnACompiledContainer() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 12d5b80f8baf3..870d448fc0c76 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -772,24 +772,18 @@ public function testCircularReferenceAllowanceForLazyServices() $dumper->dump(); } - /** - * @testWith [false] - * [true] - */ - public function testDedupLazyProxy(bool $asGhostObject) + public function testDedupLazyProxy() { $container = new ContainerBuilder(); $container->register('foo', 'stdClass')->setLazy(true)->setPublic(true); $container->register('bar', 'stdClass')->setLazy(true)->setPublic(true); + $container->register('baz', 'stdClass')->setLazy(true)->setPublic(true)->setFactory('foo_bar'); + $container->register('buz', 'stdClass')->setLazy(true)->setPublic(true)->setFactory('foo_bar'); $container->compile(); $dumper = new PhpDumper($container); - if (!$asGhostObject) { - $dumper->setProxyDumper(new \DummyProxyDumper()); - } - - $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_dedup_lazy'.($asGhostObject ? '_ghost' : '_proxy').'.php', $dumper->dump()); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_dedup_lazy.php', $dumper->dump()); } public function testLazyArgumentProvideGenerator() 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..b606736e93f02 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 @@ -6,11 +6,11 @@ namespace Container%s; include_once $this->targetDir.''.'/Fixtures/includes/foo.php'; -class FooClass_2b16075 extends \Bar\FooClass implements \Symfony\Component\VarExporter\LazyObjectInterface +class FooClassGhost2b16075 extends \Bar\FooClass implements \Symfony\Component\VarExporter\LazyObjectInterface %A -if (!\class_exists('FooClass_%s', false)) { - \class_alias(__NAMESPACE__.'\\FooClass_%s', 'FooClass_%s', false); +if (!\class_exists('FooClassGhost2b16075', false)) { + \class_alias(__NAMESPACE__.'\\FooClassGhost2b16075', 'FooClassGhost2b16075', false); } [Container%s/ProjectServiceContainer.php] => services['lazy_foo'] = $this->createProxy('FooClass_2b16075', fn () => \FooClass_2b16075::createLazyGhost($this->getLazyFooService(...))); + return $this->services['lazy_foo'] = $this->createProxy('FooClassGhost2b16075', fn () => \FooClassGhost2b16075::createLazyGhost($this->getLazyFooService(...))); } include_once $this->targetDir.''.'/Fixtures/includes/foo_lazy.php'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_ghost.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php similarity index 55% rename from src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_ghost.php rename to src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php index a7972921fadba..867cda7e7a289 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_ghost.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy.php @@ -21,6 +21,8 @@ public function __construct() $this->services = $this->privates = []; $this->methodMap = [ 'bar' => 'getBarService', + 'baz' => 'getBazService', + 'buz' => 'getBuzService', 'foo' => 'getFooService', ]; @@ -50,12 +52,40 @@ protected function createProxy($class, \Closure $factory) protected function getBarService($lazyLoad = true) { if (true === $lazyLoad) { - return $this->services['bar'] = $this->createProxy('stdClass_5a8a5eb', fn () => \stdClass_5a8a5eb::createLazyGhost($this->getBarService(...))); + return $this->services['bar'] = $this->createProxy('stdClassGhost5a8a5eb', fn () => \stdClassGhost5a8a5eb::createLazyGhost($this->getBarService(...))); } return $lazyLoad; } + /** + * Gets the public 'baz' shared service. + * + * @return \stdClass + */ + protected function getBazService($lazyLoad = true) + { + if (true === $lazyLoad) { + return $this->services['baz'] = $this->createProxy('stdClassProxy5a8a5eb', fn () => \stdClassProxy5a8a5eb::createLazyProxy(fn () => $this->getBazService(false))); + } + + return \foo_bar(); + } + + /** + * Gets the public 'buz' shared service. + * + * @return \stdClass + */ + protected function getBuzService($lazyLoad = true) + { + if (true === $lazyLoad) { + return $this->services['buz'] = $this->createProxy('stdClassProxy5a8a5eb', fn () => \stdClassProxy5a8a5eb::createLazyProxy(fn () => $this->getBuzService(false))); + } + + return \foo_bar(); + } + /** * Gets the public 'foo' shared service. * @@ -64,14 +94,14 @@ protected function getBarService($lazyLoad = true) protected function getFooService($lazyLoad = true) { if (true === $lazyLoad) { - return $this->services['foo'] = $this->createProxy('stdClass_5a8a5eb', fn () => \stdClass_5a8a5eb::createLazyGhost($this->getFooService(...))); + return $this->services['foo'] = $this->createProxy('stdClassGhost5a8a5eb', fn () => \stdClassGhost5a8a5eb::createLazyGhost($this->getFooService(...))); } return $lazyLoad; } } -class stdClass_5a8a5eb extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface +class stdClassGhost5a8a5eb extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyGhostTrait; @@ -82,3 +112,18 @@ class stdClass_5a8a5eb extends \stdClass implements \Symfony\Component\VarExport class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +class stdClassProxy5a8a5eb extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyProxyTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = [ + 'lazyObjectReal' => [self::class, 'lazyObjectReal', null], + "\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null], + ]; +} + +// 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); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_proxy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_proxy.php deleted file mode 100644 index 841c892216f68..0000000000000 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_proxy.php +++ /dev/null @@ -1,70 +0,0 @@ -services = $this->privates = []; - $this->methodMap = [ - 'bar' => 'getBarService', - '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 createProxy($class, \Closure $factory) - { - return $factory(); - } - - /** - * Gets the public 'bar' shared service. - * - * @return \stdClass - */ - protected function getBarService($lazyLoad = true) - { - // lazy factory for stdClass - - return new \stdClass(); - } - - /** - * Gets the public 'foo' shared service. - * - * @return \stdClass - */ - protected function getFooService($lazyLoad = true) - { - // lazy factory for stdClass - - return new \stdClass(); - } -} - -// proxy code 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 a52a32a27dadd..a4e8807c71039 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 @@ -22,7 +22,7 @@ class getNonSharedFooService extends ProjectServiceContainer $container->factories['non_shared_foo'] ??= fn () => self::do($container); if (true === $lazyLoad) { - return $container->createProxy('FooLazyClass_f814e3a', fn () => \FooLazyClass_f814e3a::createLazyGhost(fn ($proxy) => self::do($container, $proxy))); + return $container->createProxy('FooLazyClassGhostF814e3a', fn () => \FooLazyClassGhostF814e3a::createLazyGhost(fn ($proxy) => self::do($container, $proxy))); } static $include = true; @@ -37,11 +37,11 @@ class getNonSharedFooService extends ProjectServiceContainer } } - [Container%s/FooLazyClass_f814e3a.php] => set(\Container%s\ProjectServiceContainer::class, null); -require __DIR__.'/Container%s/FooLazyClass_f814e3a.php'; +require __DIR__.'/Container%s/FooLazyClassGhostF814e3a.php'; require __DIR__.'/Container%s/getNonSharedFooService.php'; $classes = []; 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 c304c9461fd1c..7d584eef41063 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 @@ -68,14 +68,14 @@ protected function getFooService($lazyLoad = true) $this->factories['service_container']['foo'] ??= $this->getFooService(...); if (true === $lazyLoad) { - return $this->createProxy('stdClass_5a8a5eb', fn () => \stdClass_5a8a5eb::createLazyGhost($this->getFooService(...))); + return $this->createProxy('stdClassGhost5a8a5eb', fn () => \stdClassGhost5a8a5eb::createLazyGhost($this->getFooService(...))); } return $lazyLoad; } } -class stdClass_5a8a5eb extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface +class stdClassGhost5a8a5eb extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyGhostTrait; 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 5de8772d6746e..b0bbc7b640be8 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 @@ -56,7 +56,7 @@ protected function createProxy($class, \Closure $factory) protected function getWitherService($lazyLoad = true) { if (true === $lazyLoad) { - return $this->services['wither'] = $this->createProxy('Wither_94fa281', fn () => \Wither_94fa281::createLazyProxy(fn () => $this->getWitherService(false))); + return $this->services['wither'] = $this->createProxy('WitherProxy94fa281', fn () => \WitherProxy94fa281::createLazyProxy(fn () => $this->getWitherService(false))); } $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither(); @@ -71,7 +71,7 @@ protected function getWitherService($lazyLoad = true) } } -class Wither_94fa281 extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface +class WitherProxy94fa281 extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface { use \Symfony\Component\VarExporter\LazyProxyTrait; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services10.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services10.xml index 921070e1b44a0..89b96413913d2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services10.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services10.xml @@ -11,6 +11,10 @@ other-option="lorem" an_other-option="ipsum" /> + bar_tag diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index b3de370ba370f..9207742bc25f1 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -369,13 +369,17 @@ public function testParsesIteratorArgument() $this->assertEquals([new IteratorArgument(['k1' => new Reference('foo.baz'), 'k2' => new Reference('service_container')]), new IteratorArgument([])], $lazyDefinition->getArguments(), '->load() parses lazy arguments'); } - public function testParsesTags() + /** + * @testWith ["foo_tag"] + * ["bar_tag"] + */ + public function testParsesTags(string $tag) { $container = new ContainerBuilder(); $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); $loader->load('services10.xml'); - $services = $container->findTaggedServiceIds('foo_tag'); + $services = $container->findTaggedServiceIds($tag); $this->assertCount(1, $services); foreach ($services as $id => $tagAttributes) { diff --git a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php index 5befc7fc8cbe9..69c28c114c51c 100644 --- a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php +++ b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php @@ -57,7 +57,7 @@ class DebugClassLoader 'null' => 'null', 'resource' => 'resource', 'boolean' => 'bool', - 'true' => 'bool', + 'true' => 'true', 'false' => 'false', 'integer' => 'int', 'array' => 'array', @@ -75,6 +75,7 @@ class DebugClassLoader '$this' => 'static', 'list' => 'array', 'class-string' => 'string', + 'never' => 'never', ]; private const BUILTIN_RETURN_TYPES = [ @@ -92,6 +93,9 @@ class DebugClassLoader 'parent' => true, 'mixed' => true, 'static' => true, + 'null' => true, + 'true' => true, + 'never' => true, ]; private const MAGIC_METHODS = [ @@ -796,6 +800,12 @@ private function setReturnType(string $types, string $class, string $method, str return; } + if ('null' === $types) { + self::$returnTypes[$class][$method] = ['null', 'null', $class, $filename]; + + return; + } + if ($nullable = str_starts_with($types, 'null|')) { $types = substr($types, 5); } elseif ($nullable = str_ends_with($types, '|null')) { diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php index a4d67708850a3..57f34d8fcad4a 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php @@ -50,7 +50,7 @@ public function __construct(bool|callable $debug = false, string $charset = null { $this->debug = \is_bool($debug) ? $debug : $debug(...); $this->charset = $charset ?: (\ini_get('default_charset') ?: 'UTF-8'); - $fileLinkFormat ??= $_SERVER['SYMFONY_IDE'] ?? null; + $fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? null; $this->fileLinkFormat = \is_string($fileLinkFormat) ? (ErrorRendererInterface::IDE_LINK_FORMATS[$fileLinkFormat] ?? $fileLinkFormat ?: false) : ($fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: false); diff --git a/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php b/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php index 2168a1c075816..1e8afe39b9a4e 100644 --- a/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php +++ b/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php @@ -753,6 +753,7 @@ class TentativeTypes 'isVariadic' => 'bool', 'isStatic' => 'bool', 'getClosureThis' => '?object', + 'getClosureCalledClass' => '?ReflectionClass', 'getClosureScopeClass' => '?ReflectionClass', 'getDocComment' => 'string|false', 'getEndLine' => 'int|false', diff --git a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php index fbfcdf90bd8f1..ab2ea41a08c6c 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php @@ -397,6 +397,10 @@ class_exists('Test\\'.ReturnType::class, true); 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::mixed()" might add "mixed" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::nullableMixed()" might add "mixed" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::static()" might add "static" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', + 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::false()" might add "false" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', + 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::true()" might add "true" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', + 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::never()" might add "never" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', + 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::null()" might add "null" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', ], $deprecations); } diff --git a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnType.php b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnType.php index 21c4d2012f663..1b8138001de78 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnType.php +++ b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnType.php @@ -46,5 +46,9 @@ public function this() { } public function mixed() { } public function nullableMixed() { } public function static() { } + public function false() { } + public function true() { } + public function never() { } + public function null() { } public function outsideMethod() { } } diff --git a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnTypeParent.php b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnTypeParent.php index c7b0b6f0fca0c..d42c7c8c02167 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnTypeParent.php +++ b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnTypeParent.php @@ -220,6 +220,34 @@ public function static() { } + /** + * @return false + */ + public function false() + { + } + + /** + * @return true + */ + public function true() + { + } + + /** + * @return never + */ + public function never() + { + } + + /** + * @return null + */ + public function null() + { + } + /** * @return int */ diff --git a/src/Symfony/Component/ErrorHandler/phpunit.xml.dist b/src/Symfony/Component/ErrorHandler/phpunit.xml.dist index b23ccab51b8a7..b5b52f5a5208f 100644 --- a/src/Symfony/Component/ErrorHandler/phpunit.xml.dist +++ b/src/Symfony/Component/ErrorHandler/phpunit.xml.dist @@ -10,6 +10,7 @@ > + diff --git a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php index 91f8a3dc836bc..af257fff48393 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php +++ b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php @@ -50,7 +50,7 @@ public function __construct(callable|array $listener, ?string $name, Stopwatch $ $r = new \ReflectionFunction($listener); if (str_contains($r->name, '{closure}')) { $this->pretty = $this->name = 'closure'; - } elseif ($class = $r->getClosureScopeClass()) { + } elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $this->name = $class->name; $this->pretty = $this->name.'::'.$r->name; } else { diff --git a/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php b/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php index 1bad653328965..e9b10c3295d75 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php @@ -149,4 +149,15 @@ public function toArray() return [$this->nodes['node'], '[', $this->nodes['attribute'], ']']; } } + + /** + * Provides BC with instances serialized before v6.2 + */ + public function __unserialize(array $data): void + { + $this->nodes = $data['nodes']; + $this->attributes = $data['attributes']; + $this->attributes['is_null_coalesce'] ??= false; + $this->attributes['is_short_circuited'] ??= $data["\x00Symfony\Component\ExpressionLanguage\Node\GetAttrNode\x00isShortCircuited"] ?? false; + } } diff --git a/src/Symfony/Component/HtmlSanitizer/Tests/TextSanitizer/UrlSanitizerTest.php b/src/Symfony/Component/HtmlSanitizer/Tests/TextSanitizer/UrlSanitizerTest.php index 3216244e9ed10..18fec32dee43d 100644 --- a/src/Symfony/Component/HtmlSanitizer/Tests/TextSanitizer/UrlSanitizerTest.php +++ b/src/Symfony/Component/HtmlSanitizer/Tests/TextSanitizer/UrlSanitizerTest.php @@ -45,6 +45,33 @@ public function provideSanitize() 'output' => null, ]; + yield [ + 'input' => 'http://trusted.com/link.php', + 'allowedSchemes' => null, + 'allowedHosts' => null, + 'forceHttps' => false, + 'allowRelative' => false, + 'output' => 'http://trusted.com/link.php', + ]; + + yield [ + 'input' => 'https://trusted.com/link.php', + 'allowedSchemes' => null, + 'allowedHosts' => null, + 'forceHttps' => false, + 'allowRelative' => false, + 'output' => 'https://trusted.com/link.php', + ]; + + yield [ + 'input' => 'data:text/plain;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', + 'allowedSchemes' => null, + 'allowedHosts' => null, + 'forceHttps' => false, + 'allowRelative' => false, + 'output' => 'data:text/plain;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', + ]; + yield [ 'input' => 'https://trusted.com/link.php', 'allowedSchemes' => ['https'], diff --git a/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php b/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php index a5be24dcca09b..c42d873fc4f5c 100644 --- a/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php +++ b/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php @@ -29,7 +29,7 @@ public function process(ContainerBuilder $container) $container->register('.debug.'.$id, TraceableHttpClient::class) ->setArguments([new Reference('.debug.'.$id.'.inner'), new Reference('debug.stopwatch', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]) ->addTag('kernel.reset', ['method' => 'reset']) - ->setDecoratedService($id); + ->setDecoratedService($id, null, 5); $container->getDefinition('data_collector.http_client') ->addMethodCall('registerClient', [$id, new Reference('.debug.'.$id)]); } diff --git a/src/Symfony/Component/HttpClient/Tests/DependencyInjection/HttpClientPassTest.php b/src/Symfony/Component/HttpClient/Tests/DependencyInjection/HttpClientPassTest.php index eb04f88226d1f..c6dcf4fcf7902 100755 --- a/src/Symfony/Component/HttpClient/Tests/DependencyInjection/HttpClientPassTest.php +++ b/src/Symfony/Component/HttpClient/Tests/DependencyInjection/HttpClientPassTest.php @@ -38,7 +38,7 @@ public function testItDecoratesHttpClientWithTraceableHttpClient() $sut->process($container); $this->assertTrue($container->hasDefinition('.debug.foo')); $this->assertSame(TraceableHttpClient::class, $container->getDefinition('.debug.foo')->getClass()); - $this->assertSame(['foo', null, 0], $container->getDefinition('.debug.foo')->getDecoratedService()); + $this->assertSame(['foo', null, 5], $container->getDefinition('.debug.foo')->getDecoratedService()); } public function testItRegistersDebugHttpClientToCollector() diff --git a/src/Symfony/Component/HttpFoundation/IpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php index 49f433b76cc30..746720ea78ea2 100644 --- a/src/Symfony/Component/HttpFoundation/IpUtils.php +++ b/src/Symfony/Component/HttpFoundation/IpUtils.php @@ -114,10 +114,6 @@ public static function checkIp6(string $requestIp, string $ip): bool } // Check to see if we were given a IP4 $requestIp or $ip by mistake - if (str_contains($requestIp, '.') || str_contains($ip, '.')) { - return self::$checkedIps[$cacheKey] = false; - } - if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { return self::$checkedIps[$cacheKey] = false; } @@ -125,6 +121,10 @@ public static function checkIp6(string $requestIp, string $ip): bool if (str_contains($ip, '/')) { [$address, $netmask] = explode('/', $ip, 2); + if (!filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return self::$checkedIps[$cacheKey] = false; + } + if ('0' === $netmask) { return (bool) unpack('n*', @inet_pton($address)); } @@ -133,6 +133,10 @@ public static function checkIp6(string $requestIp, string $ip): bool return self::$checkedIps[$cacheKey] = false; } } else { + if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return self::$checkedIps[$cacheKey] = false; + } + $address = $ip; $netmask = 128; } diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index a2b3409b54162..3cf8a9954007d 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -502,7 +502,7 @@ public function __toString(): string $cookies = []; foreach ($this->cookies as $k => $v) { - $cookies[] = $k.'='.$v; + $cookies[] = \is_array($v) ? http_build_query([$k => $v], '', '; ', \PHP_QUERY_RFC3986) : "$k=$v"; } if ($cookies) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php index 948033ea98a8e..2bc29b459cacd 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -66,17 +66,27 @@ protected function doRead(string $sessionId): string public function updateTimestamp(string $sessionId, string $data): bool { - $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); - $this->memcached->touch($this->prefix.$sessionId, time() + (int) $ttl); + $this->memcached->touch($this->prefix.$sessionId, $this->getCompatibleTtl()); return true; } protected function doWrite(string $sessionId, string $data): bool + { + return $this->memcached->set($this->prefix.$sessionId, $data, $this->getCompatibleTtl()); + } + + private function getCompatibleTtl(): int { $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); - return $this->memcached->set($this->prefix.$sessionId, $data, time() + (int) $ttl); + // If the relative TTL that is used exceeds 30 days, memcached will treat the value as Unix time. + // We have to convert it to an absolute Unix time at this point, to make sure the TTL is correct. + if ($ttl > 60 * 60 * 24 * 30) { + $ttl += time(); + } + + return $ttl; } protected function doDestroy(string $sessionId): bool diff --git a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php index 9db54719a65b5..6085331b28bf3 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php @@ -78,6 +78,7 @@ public function getIpv6Data() [false, '0.0.0.0/8', '::1'], [false, '::1', '127.0.0.1'], [false, '::1', '0.0.0.0/8'], + [true, '::ffff:10.126.42.2', '::ffff:10.0.0.0/0'], ]; } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 9a0892c12e3d1..23acd2e05f7fe 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1721,6 +1721,12 @@ public function testToString() $asString = (string) $request; $this->assertStringContainsString('Cookie: Foo=Bar; Another=Cookie', $asString); + + $request->cookies->set('foo.bar', [1, 2]); + + $asString = (string) $request; + + $this->assertStringContainsString('foo.bar%5B0%5D=1; foo.bar%5B1%5D=2', $asString); } public function testIsMethod() 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 eac380fd9094a..3e0e7844104b4 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php @@ -16,6 +16,7 @@ /** * @requires extension memcached + * * @group time-sensitive */ class MemcachedSessionHandlerTest extends TestCase @@ -92,13 +93,30 @@ public function testWriteSession() $this->memcached ->expects($this->once()) ->method('set') - ->with(self::PREFIX.'id', 'data', $this->equalTo(time() + self::TTL, 2)) + ->with(self::PREFIX.'id', 'data', $this->equalTo(self::TTL, 2)) ->willReturn(true) ; $this->assertTrue($this->storage->write('id', 'data')); } + public function testWriteSessionWithLargeTTL() + { + $this->memcached + ->expects($this->once()) + ->method('set') + ->with(self::PREFIX.'id', 'data', $this->equalTo(time() + self::TTL + 60 * 60 * 24 * 30, 2)) + ->willReturn(true) + ; + + $storage = new MemcachedSessionHandler( + $this->memcached, + ['prefix' => self::PREFIX, 'expiretime' => self::TTL + 60 * 60 * 24 * 30] + ); + + $this->assertTrue($storage->write('id', 'data')); + } + public function testDestroySession() { $this->memcached diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index b7b7d981c140c..aa505783bbd46 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -471,7 +471,7 @@ private function parseController(array|object|string|null $controller): array|st } $controller['method'] = $r->name; - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $controller['class'] = $class->name; } else { return $r->name; diff --git a/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php b/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php index 094465274181b..4eaeeb1513713 100644 --- a/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php +++ b/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php @@ -35,7 +35,8 @@ class FileLinkFormatter */ public function __construct(string|array $fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, string|\Closure $urlFormat = null) { - $fileLinkFormat ??= $_SERVER['SYMFONY_IDE'] ?? ''; + $fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? ''; + 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/Event/ControllerEvent.php b/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php index efbdb944800ff..b012263b93a61 100644 --- a/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php +++ b/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php @@ -92,7 +92,7 @@ public function getAttributes(): array } elseif (\is_string($this->controller) && false !== $i = strpos($this->controller, '::')) { $class = new \ReflectionClass(substr($this->controller, 0, $i)); } else { - $class = str_contains($this->controllerReflector->name, '{closure}') ? null : $this->controllerReflector->getClosureScopeClass(); + $class = str_contains($this->controllerReflector->name, '{closure}') ? null : (\PHP_VERSION_ID >= 80111 ? $this->controllerReflector->getClosureCalledClass() : $this->controllerReflector->getClosureScopeClass()); } $this->attributes = []; diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php index 5e6b70d10839e..c41d6fbef37ab 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php @@ -195,10 +195,11 @@ public function onKernelResponse(ResponseEvent $event) } if ($autoCacheControl) { + $maxAge = $response->headers->hasCacheControlDirective('public') ? 0 : (int) $response->getMaxAge(); $response - ->setExpires(new \DateTimeImmutable()) + ->setExpires(new \DateTimeImmutable('+'.$maxAge.' seconds')) ->setPrivate() - ->setMaxAge(0) + ->setMaxAge($maxAge) ->headers->addCacheControlDirective('must-revalidate'); } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 0e99228e0937f..358dab3aed06a 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -75,11 +75,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.2.1'; - public const VERSION_ID = 60201; + public const VERSION = '6.2.2'; + public const VERSION_ID = 60202; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 2; - public const RELEASE_VERSION = 1; + public const RELEASE_VERSION = 2; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '07/2023'; diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php index 302f9e35dd70d..d6be9d3bd023c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -467,7 +467,7 @@ public function testAutowireAttribute() $container = new ContainerBuilder(); $resolver = $container->register('argument_resolver.service', 'stdClass')->addArgument([]); - $container->register('some.id', \stdClass::class); + $container->register('some.id', \stdClass::class)->setPublic(true); $container->setParameter('some.parameter', 'foo'); $container->register('foo', WithAutowireAttribute::class) diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php index ca28b40eec2a4..7dea0f85c94a7 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php @@ -38,6 +38,7 @@ class SessionListenerTest extends TestCase { /** * @dataProvider provideSessionOptions + * * @runInSeparateProcess */ public function testSessionCookieOptions(array $phpSessionOptions, array $sessionOptions, array $expectedSessionOptions) @@ -554,6 +555,69 @@ public function testUninitializedSessionWithoutInitializedSession() $this->assertSame('60', $response->headers->getCacheControlDirective('s-maxage')); } + public function testResponseHeadersMaxAgeAndExpiresNotBeOverridenIfSessionStarted() + { + $session = $this->createMock(Session::class); + $session->expects($this->once())->method('getUsageIndex')->willReturn(1); + $session->expects($this->once())->method('getName')->willReturn('foo'); + $sessionFactory = $this->createMock(SessionFactory::class); + $sessionFactory->expects($this->once())->method('createSession')->willReturn($session); + + $container = new Container(); + $container->set('session_factory', $sessionFactory); + + $listener = new SessionListener($container); + $kernel = $this->createMock(HttpKernelInterface::class); + + $request = new Request(); + $listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST)); + + $request->getSession(); + + $response = new Response(); + $response->setPrivate(); + $expiresHeader = gmdate('D, d M Y H:i:s', time() + 600).' GMT'; + $response->setMaxAge(600); + $listener->onKernelResponse(new ResponseEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $response)); + + $this->assertTrue($response->headers->has('expires')); + $this->assertSame($expiresHeader, $response->headers->get('expires')); + $this->assertFalse($response->headers->has('max-age')); + $this->assertSame('600', $response->headers->getCacheControlDirective('max-age')); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + $this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($response->headers->has(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER)); + } + + public function testResponseHeadersMaxAgeAndExpiresDefaultValuesIfSessionStarted() + { + $session = $this->createMock(Session::class); + $session->expects($this->once())->method('getUsageIndex')->willReturn(1); + + $container = new Container(); + + $listener = new SessionListener($container); + $kernel = $this->createMock(HttpKernelInterface::class); + + $request = new Request(); + $request->setSession($session); + $listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST)); + + $response = new Response(); + $expiresHeader = gmdate('D, d M Y H:i:s', time()).' GMT'; + $listener->onKernelResponse(new ResponseEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $response)); + + $this->assertTrue($response->headers->has('expires')); + $this->assertSame($expiresHeader, $response->headers->get('expires')); + $this->assertFalse($response->headers->has('max-age')); + $this->assertSame('0', $response->headers->getCacheControlDirective('max-age')); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + $this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($response->headers->has(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER)); + } + public function testSurrogateMainRequestIsPublic() { $session = $this->createMock(Session::class); diff --git a/src/Symfony/Component/HttpKernel/phpunit.xml.dist b/src/Symfony/Component/HttpKernel/phpunit.xml.dist index 7e2c738f869f1..a72dcbcca2979 100644 --- a/src/Symfony/Component/HttpKernel/phpunit.xml.dist +++ b/src/Symfony/Component/HttpKernel/phpunit.xml.dist @@ -10,6 +10,7 @@ > + diff --git a/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php index b6828bbaac76d..78d53b994a0c8 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mailer\Exception\TransportException; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use Symfony\Component\Mailer\Transport\RoundRobinTransport; use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Mime\RawMessage; @@ -60,10 +61,21 @@ public function testSendAllDead() $t2 = $this->createMock(TransportInterface::class); $t2->expects($this->once())->method('send')->will($this->throwException(new TransportException())); $t = new RoundRobinTransport([$t1, $t2]); - $this->expectException(TransportException::class); - $this->expectExceptionMessage('All transports failed.'); - $t->send(new RawMessage('')); - $this->assertTransports($t, 1, [$t1, $t2]); + $p = new \ReflectionProperty($t, 'cursor'); + $p->setAccessible(true); + $p->setValue($t, 0); + + try { + $t->send(new RawMessage('')); + } catch (\Exception $e) { + $this->assertInstanceOf(TransportException::class, $e); + $this->assertStringContainsString('All transports failed.', $e->getMessage()); + $this->assertTransports($t, 0, [$t1, $t2]); + + return; + } + + $this->fail('The expected exception was not thrown.'); } public function testSendOneDead() @@ -124,6 +136,34 @@ public function testSendOneDeadAndRecoveryWithinRetryPeriod() $this->assertTransports($t, 1, []); } + public function testFailureDebugInformation() + { + $t1 = $this->createMock(TransportInterface::class); + $e1 = new TransportException(); + $e1->appendDebug('Debug message 1'); + $t1->expects($this->once())->method('send')->will($this->throwException($e1)); + $t1->expects($this->once())->method('__toString')->willReturn('t1'); + + $t2 = $this->createMock(TransportInterface::class); + $e2 = new TransportException(); + $e2->appendDebug('Debug message 2'); + $t2->expects($this->once())->method('send')->will($this->throwException($e2)); + $t2->expects($this->once())->method('__toString')->willReturn('t2'); + + $t = new RoundRobinTransport([$t1, $t2]); + + try { + $t->send(new RawMessage('')); + } catch (TransportExceptionInterface $e) { + $this->assertStringContainsString('Transport "t1": Debug message 1', $e->getDebug()); + $this->assertStringContainsString('Transport "t2": Debug message 2', $e->getDebug()); + + return; + } + + $this->fail('Expected exception was not thrown!'); + } + private function assertTransports(RoundRobinTransport $transport, int $cursor, array $deadTransports) { $p = new \ReflectionProperty($transport, 'cursor'); diff --git a/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php b/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php index ab0e40458b658..c5587bb2a3585 100644 --- a/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php +++ b/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php @@ -48,15 +48,19 @@ public function __construct(array $transports, int $retryPeriod = 60) public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage { + $exception = null; + while ($transport = $this->getNextTransport()) { try { return $transport->send($message, $envelope); - } catch (TransportExceptionInterface) { + } catch (TransportExceptionInterface $e) { + $exception ??= new TransportException('All transports failed.'); + $exception->appendDebug(sprintf("Transport \"%s\": %s\n", $transport, $e->getDebug())); $this->deadTransports[$transport] = microtime(true); } } - throw new TransportException('All transports failed.'); + throw $exception ?? new TransportException('No transports found.'); } public function __toString(): string diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php index ec71f60f1394a..353d56a2ddb6c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php @@ -32,6 +32,9 @@ class Connection 'x-message-ttl', ]; + /** + * @see https://github.com/php-amqp/php-amqp/blob/master/amqp_connection_resource.h + */ private const AVAILABLE_OPTIONS = [ 'host', 'port', @@ -52,6 +55,7 @@ class Connection 'write_timeout', 'confirm_timeout', 'connect_timeout', + 'rpc_timeout', 'cacert', 'cert', 'key', diff --git a/src/Symfony/Component/Messenger/Handler/HandlerDescriptor.php b/src/Symfony/Component/Messenger/Handler/HandlerDescriptor.php index 260a4a7a6ca38..821754b37bb81 100644 --- a/src/Symfony/Component/Messenger/Handler/HandlerDescriptor.php +++ b/src/Symfony/Component/Messenger/Handler/HandlerDescriptor.php @@ -35,7 +35,7 @@ public function __construct(callable $handler, array $options = []) if (str_contains($r->name, '{closure}')) { $this->name = 'Closure'; } elseif (!$handler = $r->getClosureThis()) { - $class = $r->getClosureScopeClass(); + $class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass(); $this->name = ($class ? $class->name.'::' : '').$r->name; } else { diff --git a/src/Symfony/Component/Mime/Part/MessagePart.php b/src/Symfony/Component/Mime/Part/MessagePart.php index 1b5c23e2bc411..270d57aa343ac 100644 --- a/src/Symfony/Component/Mime/Part/MessagePart.php +++ b/src/Symfony/Component/Mime/Part/MessagePart.php @@ -59,4 +59,14 @@ public function bodyToIterable(): iterable { return $this->message->toIterable(); } + + public function __sleep(): array + { + return ['message']; + } + + public function __wakeup() + { + $this->__construct($this->message); + } } diff --git a/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php b/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php index 2713d5bc079c7..c01958a4b94b8 100644 --- a/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php +++ b/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php @@ -39,4 +39,14 @@ public function testHeaders() new ParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'Subject.eml', 'filename' => 'Subject.eml']) ), $p->getPreparedHeaders()); } + + public function testSerialize() + { + $email = (new Email())->from('fabien@symfony.com')->to('you@example.com')->text('content'); + $email->getHeaders()->addIdHeader('Message-ID', $email->generateMessageId()); + + $p = new MessagePart($email); + $expected = clone $p; + $this->assertEquals($expected->toString(), unserialize(serialize($p))->toString()); + } } diff --git a/src/Symfony/Component/Notifier/Bridge/Clickatell/composer.json b/src/Symfony/Component/Notifier/Bridge/Clickatell/composer.json index e3f3227a9c278..a70b66590e6a8 100644 --- a/src/Symfony/Component/Notifier/Bridge/Clickatell/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Clickatell/composer.json @@ -24,9 +24,6 @@ "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, - "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Clickatell\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/composer.json b/src/Symfony/Component/Notifier/Bridge/Discord/composer.json index 62ee37ffa0f8a..2d857b661a696 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Discord/composer.json @@ -21,9 +21,6 @@ "symfony/notifier": "^6.2", "symfony/polyfill-mbstring": "^1.0" }, - "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Discord\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/Engagespot/composer.json b/src/Symfony/Component/Notifier/Bridge/Engagespot/composer.json index b477f5865b43e..d3718f2891e1a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Engagespot/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Engagespot/composer.json @@ -16,8 +16,8 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/http-client": "^4.3|^5.0|^6.0", + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, "autoload": { diff --git a/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json b/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json index 6518b750a70fd..aa4baaefe1d72 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json @@ -24,7 +24,6 @@ "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" }, "autoload": { diff --git a/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json b/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json index 5ec84dd6dd401..0df04f9ac04af 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json @@ -24,7 +24,6 @@ "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" }, "autoload": { diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/composer.json b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/composer.json index 181d46226a626..aee8470e119fb 100644 --- a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/composer.json @@ -1,32 +1,32 @@ { - "name": "symfony/kaz-info-teh-notifier", - "type": "symfony-bridge", - "description": "Symfony KazInfoTeh Notifier Bridge", - "keywords": ["KazInfoTeh", "notifier", "symfony", "sms"], - "homepage": "https://symfony.com", - "license": "MIT", - "authors": [ - { - "name": "Egor Taranov", - "email": "dev@taranovegor.com", - "homepage": "https://taranovegor.com/contribution" + "name": "symfony/kaz-info-teh-notifier", + "type": "symfony-bridge", + "description": "Symfony KazInfoTeh Notifier Bridge", + "keywords": ["KazInfoTeh", "notifier", "symfony", "sms"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Egor Taranov", + "email": "dev@taranovegor.com", + "homepage": "https://taranovegor.com/contribution" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "ext-simplexml": "*", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.2" }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "require": { - "php": ">=8.1", - "ext-simplexml": "*", - "symfony/http-client": "^5.4|^6.0", - "symfony/notifier": "^6.2" - }, - "autoload": { - "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\KazInfoTeh\\": "" }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "minimum-stability": "dev" + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\KazInfoTeh\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" } diff --git a/src/Symfony/Component/Notifier/Bridge/MessageMedia/composer.json b/src/Symfony/Component/Notifier/Bridge/MessageMedia/composer.json index 8905c9ca932bd..2d76e1d99dd39 100644 --- a/src/Symfony/Component/Notifier/Bridge/MessageMedia/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/MessageMedia/composer.json @@ -1,30 +1,30 @@ { - "name": "symfony/message-media-notifier", - "type": "symfony-notifier-bridge", - "description": "Symfony MessageMedia Notifier Bridge", - "keywords": ["sms", "messagemedia", "notifier"], - "homepage": "https://symfony.com", - "license": "MIT", - "authors": [ - { - "name": "Adrian Nguyen", - "email": "vuphuong87@gmail.com" + "name": "symfony/message-media-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony MessageMedia Notifier Bridge", + "keywords": ["sms", "messagemedia", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Adrian Nguyen", + "email": "vuphuong87@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.2" }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "require": { - "php": ">=8.1", - "symfony/http-client": "^5.4|^6.0", - "symfony/notifier": "^6.2" - }, - "autoload": { - "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\MessageMedia\\": "" }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "minimum-stability": "dev" + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\MessageMedia\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" } diff --git a/src/Symfony/Component/Notifier/Bridge/OneSignal/composer.json b/src/Symfony/Component/Notifier/Bridge/OneSignal/composer.json index 9175b3c3f7301..bee9e12596252 100644 --- a/src/Symfony/Component/Notifier/Bridge/OneSignal/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/OneSignal/composer.json @@ -20,9 +20,6 @@ "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, - "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\OneSignal\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/Sendberry/composer.json b/src/Symfony/Component/Notifier/Bridge/Sendberry/composer.json index 6edfc02be3a62..4f74162ef634a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Sendberry/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Sendberry/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/composer.json b/src/Symfony/Component/Notifier/Bridge/Slack/composer.json index eb2e71e9ccfb0..ca14593031acf 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Slack/composer.json @@ -20,9 +20,6 @@ "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, - "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Slack\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json b/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json index 7a1204c82390b..65818cb9f360a 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json @@ -20,9 +20,6 @@ "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, - "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Telegram\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/Vonage/composer.json b/src/Symfony/Component/Notifier/Bridge/Vonage/composer.json index 9adbbc558c5c3..013c59be0fc82 100644 --- a/src/Symfony/Component/Notifier/Bridge/Vonage/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Vonage/composer.json @@ -16,8 +16,8 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/http-client": "^4.3|^5.0|^6.0", + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", "symfony/notifier": "^6.2" }, "autoload": { diff --git a/src/Symfony/Component/RateLimiter/Reservation.php b/src/Symfony/Component/RateLimiter/Reservation.php index 9e6029aa01251..f73b74bc263f7 100644 --- a/src/Symfony/Component/RateLimiter/Reservation.php +++ b/src/Symfony/Component/RateLimiter/Reservation.php @@ -45,6 +45,6 @@ public function getRateLimit(): RateLimit public function wait(): void { - usleep($this->getWaitDuration() * 1e6); + usleep((int) ($this->getWaitDuration() * 1e6)); } } diff --git a/src/Symfony/Component/Security/Core/Security.php b/src/Symfony/Component/Security/Core/Security.php index f1ebf822f5bac..97f1c8ce1f568 100644 --- a/src/Symfony/Component/Security/Core/Security.php +++ b/src/Symfony/Component/Security/Core/Security.php @@ -26,22 +26,16 @@ class Security implements AuthorizationCheckerInterface { /** * @deprecated since Symfony 6.2, use \Symfony\Bundle\SecurityBundle\Security::ACCESS_DENIED_ERROR instead - * - * In 7.0, move this constant to the NewSecurityHelper class and make it reference SecurityRequestAttributes::ACCESS_DENIED_ERROR. */ public const ACCESS_DENIED_ERROR = '_security.403_error'; /** * @deprecated since Symfony 6.2, use \Symfony\Bundle\SecurityBundle\Security::AUTHENTICATION_ERROR instead - * - * In 7.0, move this constant to the NewSecurityHelper class and make it reference SecurityRequestAttributes::AUTHENTICATION_ERROR. */ public const AUTHENTICATION_ERROR = '_security.last_error'; /** * @deprecated since Symfony 6.2, use \Symfony\Bundle\SecurityBundle\Security::LAST_USERNAME instead - * - * In 7.0, move this constant to the NewSecurityHelper class and make it reference SecurityRequestAttributes::LAST_USERNAME. */ public const LAST_USERNAME = '_security.last_username'; diff --git a/src/Symfony/Component/Security/Core/User/UserInterface.php b/src/Symfony/Component/Security/Core/User/UserInterface.php index c9b0930b19ffc..cace8f6aed6cf 100644 --- a/src/Symfony/Component/Security/Core/User/UserInterface.php +++ b/src/Symfony/Component/Security/Core/User/UserInterface.php @@ -55,7 +55,7 @@ public function getRoles(): array; public function eraseCredentials(); /** - * Returns the identifier for this user (e.g. its username or email address). + * Returns the identifier for this user (e.g. username or email address). */ public function getUserIdentifier(): string; } diff --git a/src/Symfony/Component/Security/Http/EventListener/IsGrantedAttributeListener.php b/src/Symfony/Component/Security/Http/EventListener/IsGrantedAttributeListener.php index ce8fbacb8c1cc..518578b511c9b 100644 --- a/src/Symfony/Component/Security/Http/EventListener/IsGrantedAttributeListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/IsGrantedAttributeListener.php @@ -77,7 +77,7 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event) public static function getSubscribedEvents(): array { - return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 10]]; + return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 20]]; } private function getIsGrantedSubject(string|Expression $subjectRef, Request $request, array $arguments): mixed diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 12c778cb803af..829e178407bd2 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -331,8 +331,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params = []; foreach ($constructorParameters as $constructorParameter) { $paramName = $constructorParameter->name; + $attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context); $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName; - $attributeContext = $this->getAttributeDenormalizationContext($class, $key, $context); $allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes); $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context); diff --git a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php index ba39b41ff8d4d..71459dd945df7 100644 --- a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php @@ -117,17 +117,17 @@ protected function getAttributeValue(object $object, string $attribute, string $ $ucfirsted = ucfirst($attribute); $getter = 'get'.$ucfirsted; - if (\is_callable([$object, $getter])) { + if (method_exists($object, $getter) && \is_callable([$object, $getter])) { return $object->$getter(); } $isser = 'is'.$ucfirsted; - if (\is_callable([$object, $isser])) { + if (method_exists($object, $isser) && \is_callable([$object, $isser])) { return $object->$isser(); } $haser = 'has'.$ucfirsted; - if (\is_callable([$object, $haser])) { + if (method_exists($object, $haser) && \is_callable([$object, $haser])) { return $object->$haser(); } @@ -140,7 +140,14 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v $key = $object::class.':'.$setter; if (!isset(self::$setterAccessibleCache[$key])) { - self::$setterAccessibleCache[$key] = \is_callable([$object, $setter]) && !(new \ReflectionMethod($object, $setter))->isStatic(); + try { + // We have to use is_callable() here since method_exists() + // does not "see" protected/private methods + self::$setterAccessibleCache[$key] = \is_callable([$object, $setter]) && !(new \ReflectionMethod($object, $setter))->isStatic(); + } catch (\ReflectionException $e) { + // Method does not exist in the class, probably a magic method + self::$setterAccessibleCache[$key] = false; + } } if (self::$setterAccessibleCache[$key]) { diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index 9c8ba3ffcf4e2..4cc6686586e89 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Annotation\SerializedPath; @@ -32,6 +33,7 @@ use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; @@ -41,6 +43,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux; +use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummyWithContextAttribute; class AbstractObjectNormalizerTest extends TestCase { @@ -525,6 +528,20 @@ public function testDenormalizeRecursiveWithObjectAttributeWithStringValue() $this->assertInstanceOf(ObjectInner::class, $obj->getInner()); } + + public function testDenormalizeUsesContextAttributeForPropertiesInConstructorWithSeralizedName() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + + $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); + $serializer = new Serializer([new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'd-m-Y']), $normalizer]); + + /** @var ObjectDummyWithContextAttribute $obj */ + $obj = $serializer->denormalize(['property_with_serialized_name' => '01-02-2022', 'propertyWithoutSerializedName' => '01-02-2022'], ObjectDummyWithContextAttribute::class); + + $this->assertSame($obj->propertyWithSerializedName->format('Y-m-d'), $obj->propertyWithoutSerializedName->format('Y-m-d')); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectDummyWithContextAttribute.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectDummyWithContextAttribute.php new file mode 100644 index 0000000000000..b846f042fe620 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectDummyWithContextAttribute.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\Serializer\Tests\Normalizer\Features; + +use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; + +final class ObjectDummyWithContextAttribute +{ + public function __construct( + #[Context([DateTimeNormalizer::FORMAT_KEY => 'm-d-Y'])] + #[SerializedName('property_with_serialized_name')] + public \DateTimeImmutable $propertyWithSerializedName, + + #[Context([DateTimeNormalizer::FORMAT_KEY => 'm-d-Y'])] + public \DateTimeImmutable $propertyWithoutSerializedName, + ) { + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php index 6fd430bb47a43..e6f8396fe9d15 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -453,6 +453,22 @@ public function testHasGetterNormalize() ); } + public function testCallMagicMethodDenormalize() + { + $obj = $this->normalizer->denormalize(['active' => true], ObjectWithMagicMethod::class); + $this->assertTrue($obj->isActive()); + } + + public function testCallMagicMethodNormalize() + { + $obj = new ObjectWithMagicMethod(); + + $this->assertSame( + ['active' => true], + $this->normalizer->normalize($obj, 'any') + ); + } + protected function getObjectCollectionWithExpectedArray(): array { return [[ @@ -722,3 +738,18 @@ public function hasFoo() return $this->foo; } } + +class ObjectWithMagicMethod +{ + private $active = true; + + public function isActive() + { + return $this->active; + } + + public function __call($key, $value) + { + throw new \RuntimeException('__call should not be called. Called with: '.$key); + } +} diff --git a/src/Symfony/Component/String/LazyString.php b/src/Symfony/Component/String/LazyString.php index 77d50fbbb3ba1..9523b8cdb2248 100644 --- a/src/Symfony/Component/String/LazyString.php +++ b/src/Symfony/Component/String/LazyString.php @@ -127,7 +127,7 @@ private static function getPrettyName(callable $callback): string } elseif ($callback instanceof \Closure) { $r = new \ReflectionFunction($callback); - if (str_contains($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) { + if (str_contains($r->name, '{closure}') || !$class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { return $r->name; } diff --git a/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php b/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php index 8d5bd8e64eb4b..62e7a3da1622b 100644 --- a/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php +++ b/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php @@ -42,32 +42,25 @@ protected function addMessageToCatalogue(string $message, ?string $domain, int $ protected function getStringArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node, int|string $index, bool $indexIsRegex = false): array { - $args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args; - if (\is_string($index)) { return $this->getStringNamedArguments($node, $index, $indexIsRegex); } - if (\count($args) < $index) { - return []; - } + $args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args; - /** @var Node\Arg $arg */ - $arg = $args[$index]; - if (!$result = $this->getStringValue($arg->value)) { + if (!($arg = $args[$index] ?? null) instanceof Node\Arg) { return []; } - return [$result]; + return (array) $this->getStringValue($arg->value); } protected function hasNodeNamedArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node): bool { - $args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args; + $args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args; - /** @var Node\Arg $arg */ foreach ($args as $arg) { - if (null !== $arg->name) { + if ($arg instanceof Node\Arg && null !== $arg->name) { return true; } } diff --git a/src/Symfony/Component/Translation/Tests/fixtures/extractor-ast/translation.html.php b/src/Symfony/Component/Translation/Tests/fixtures/extractor-ast/translation.html.php index 781da00b64f9e..e34863f3dc660 100644 --- a/src/Symfony/Component/Translation/Tests/fixtures/extractor-ast/translation.html.php +++ b/src/Symfony/Component/Translation/Tests/fixtures/extractor-ast/translation.html.php @@ -54,3 +54,5 @@ trans('variable-assignation-inlined-in-trans-method-call3', [], $domain = 'not_messages'); ?> trans(domain: $domain = 'not_messages', message: $key = 'variable-assignation-inlined-with-named-arguments-in-trans-method', parameters: $parameters = []); ?> + +trans(...); // should not fail ?> diff --git a/src/Symfony/Component/Validator/ConstraintValidatorFactory.php b/src/Symfony/Component/Validator/ConstraintValidatorFactory.php index 8152ad0dece11..778e202a84bf8 100644 --- a/src/Symfony/Component/Validator/ConstraintValidatorFactory.php +++ b/src/Symfony/Component/Validator/ConstraintValidatorFactory.php @@ -26,20 +26,17 @@ class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface { protected $validators = []; - public function __construct() + public function __construct(array $validators = []) { + $this->validators = $validators; } public function getInstance(Constraint $constraint): ConstraintValidatorInterface { - $className = $constraint->validatedBy(); - - if (!isset($this->validators[$className])) { - $this->validators[$className] = 'validator.expression' === $className - ? new ExpressionValidator() - : new $className(); + if ('validator.expression' === $name = $class = $constraint->validatedBy()) { + $class = ExpressionValidator::class; } - return $this->validators[$className]; + return $this->validators[$name] ??= new $class(); } } diff --git a/src/Symfony/Component/Validator/Tests/ConstraintValidatorFactoryTest.php b/src/Symfony/Component/Validator/Tests/ConstraintValidatorFactoryTest.php new file mode 100644 index 0000000000000..d7d9b302d1bb7 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/ConstraintValidatorFactoryTest.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\Validator\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Validator\ConstraintValidatorFactory; +use Symfony\Component\Validator\Tests\Fixtures\DummyConstraint; +use Symfony\Component\Validator\Tests\Fixtures\DummyConstraintValidator; + +class ConstraintValidatorFactoryTest extends TestCase +{ + public function testGetInstance() + { + $factory = new ConstraintValidatorFactory(); + $this->assertInstanceOf(DummyConstraintValidator::class, $factory->getInstance(new DummyConstraint())); + } + + public function testPredefinedGetInstance() + { + $validator = new DummyConstraintValidator(); + $factory = new ConstraintValidatorFactory([DummyConstraintValidator::class => $validator]); + $this->assertSame($validator, $factory->getInstance(new DummyConstraint())); + } +} diff --git a/src/Symfony/Component/Validator/Tests/ContainerConstraintValidatorFactoryTest.php b/src/Symfony/Component/Validator/Tests/ContainerConstraintValidatorFactoryTest.php index 3999b86b4f196..63b7f6f96ae01 100644 --- a/src/Symfony/Component/Validator/Tests/ContainerConstraintValidatorFactoryTest.php +++ b/src/Symfony/Component/Validator/Tests/ContainerConstraintValidatorFactoryTest.php @@ -15,9 +15,10 @@ use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Blank as BlankConstraint; -use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\ContainerConstraintValidatorFactory; use Symfony\Component\Validator\Exception\ValidatorException; +use Symfony\Component\Validator\Tests\Fixtures\DummyConstraint; +use Symfony\Component\Validator\Tests\Fixtures\DummyConstraintValidator; class ContainerConstraintValidatorFactoryTest extends TestCase { @@ -59,18 +60,3 @@ public function testGetInstanceInvalidValidatorClass() $factory->getInstance($constraint); } } - -class DummyConstraint extends Constraint -{ - public function validatedBy(): string - { - return DummyConstraintValidator::class; - } -} - -class DummyConstraintValidator extends ConstraintValidator -{ - public function validate($value, Constraint $constraint) - { - } -} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/DummyConstraint.php b/src/Symfony/Component/Validator/Tests/Fixtures/DummyConstraint.php new file mode 100644 index 0000000000000..c2209f135f5ce --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/DummyConstraint.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +use Symfony\Component\Validator\Constraint; + +class DummyConstraint extends Constraint +{ + public function validatedBy(): string + { + return DummyConstraintValidator::class; + } +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/DummyConstraintValidator.php b/src/Symfony/Component/Validator/Tests/Fixtures/DummyConstraintValidator.php new file mode 100644 index 0000000000000..3481a5e2d8c1f --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/DummyConstraintValidator.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; + +class DummyConstraintValidator extends ConstraintValidator +{ + public function validate($value, Constraint $constraint) + { + } +} diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php index c2bbbd66ee14e..cd9b35f77b2ea 100644 --- a/src/Symfony/Component/VarDumper/Caster/Caster.php +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -47,7 +47,7 @@ public static function castObject(object $obj, string $class, bool $hasDebugInfo if ($hasDebugInfo) { try { $debugInfo = $obj->__debugInfo(); - } catch (\Exception) { + } catch (\Throwable) { // ignore failing __debugInfo() $hasDebugInfo = false; } diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index e12b78cea1ba2..f6c8c7410aed1 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -197,7 +197,7 @@ public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, arra self::addMap($a, $c, [ 'returnsReference' => 'returnsReference', 'returnType' => 'getReturnType', - 'class' => 'getClosureScopeClass', + 'class' => \PHP_VERSION_ID >= 80111 ? 'getClosureCalledClass' : 'getClosureScopeClass', 'this' => 'getClosureThis', ]); diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php index c39e82cf6adb0..f4be025c351fe 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php @@ -175,4 +175,14 @@ public function testAnonymousClass() , $c ); } + + public function testTypeErrorInDebugInfo() + { + $this->assertDumpMatchesFormat('class@anonymous {}', new class() { + public function __debugInfo(): array + { + return ['class' => \get_class(null)]; + } + }); + } } diff --git a/src/Symfony/Component/VarExporter/LazyGhostTrait.php b/src/Symfony/Component/VarExporter/LazyGhostTrait.php index 21bef1cb5b495..a0dea4dd5b8ee 100644 --- a/src/Symfony/Component/VarExporter/LazyGhostTrait.php +++ b/src/Symfony/Component/VarExporter/LazyGhostTrait.php @@ -193,17 +193,37 @@ public function &__get($name): mixed get_in_scope: - if (null === $scope) { - if (null === $readonlyScope) { - return $this->$name; + try { + if (null === $scope) { + if (null === $readonlyScope) { + return $this->$name; + } + $value = $this->$name; + + return $value; } - $value = $this->$name; + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - return $value; - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + return $accessor['get']($this, $name, null !== $readonlyScope); + } catch (\Error $e) { + if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { + throw $e; + } + + try { + if (null === $scope) { + $this->$name = []; + + return $this->$name; + } - return $accessor['get']($this, $name, null !== $readonlyScope); + $accessor['set']($this, $name, []); + + return $accessor['get']($this, $name, null !== $readonlyScope); + } catch (\Error) { + throw $e; + } + } } public function __set($name, $value): void diff --git a/src/Symfony/Component/VarExporter/LazyProxyTrait.php b/src/Symfony/Component/VarExporter/LazyProxyTrait.php index 196da7d608e07..fa6a741fd6d40 100644 --- a/src/Symfony/Component/VarExporter/LazyProxyTrait.php +++ b/src/Symfony/Component/VarExporter/LazyProxyTrait.php @@ -130,17 +130,37 @@ public function &__get($name): mixed get_in_scope: - if (null === $scope) { - if (null === $readonlyScope && 1 !== $parent) { - return $instance->$name; + try { + if (null === $scope) { + if (null === $readonlyScope && 1 !== $parent) { + return $instance->$name; + } + $value = $instance->$name; + + return $value; } - $value = $instance->$name; + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - return $value; - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent); + } catch (\Error $e) { + if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { + throw $e; + } + + try { + if (null === $scope) { + $instance->$name = []; + + return $instance->$name; + } - return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent); + $accessor['set']($instance, $name, []); + + return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent); + } catch (\Error) { + throw $e; + } + } } public function __set($name, $value): void @@ -337,7 +357,7 @@ public function __unserialize(array $data): void PublicHydrator::hydrate($this, $data); if (Registry::$parentMethods[$class]['wakeup']) { - parent:__wakeup(); + parent::__wakeup(); } } } diff --git a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php index 25673fb073ca8..3ef2a8c95c1c2 100644 --- a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php +++ b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\VarExporter\Internal\LazyObjectState; +use Symfony\Component\VarExporter\ProxyHelper; use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ChildMagicClass; use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ChildStdClass; use Symfony\Component\VarExporter\Tests\Fixtures\LazyGhost\ChildTestClass; @@ -393,4 +394,41 @@ public function testFullInitializationAfterPartialInitialization() $this->assertSame(3, ((array) $instance)["\0".TestClass::class."\0private"]); $this->assertSame(1001, $counter); } + + public function testIndirectModification() + { + $obj = new class() { + public array $foo; + }; + $proxy = $this->createLazyGhost($obj::class, fn () => null); + + $proxy->foo[] = 123; + + $this->assertSame([123], $proxy->foo); + } + + /** + * @template T + * + * @param class-string $class + * + * @return T + */ + private function createLazyGhost(string $class, \Closure|array $initializer, array $skippedProperties = null): object + { + $r = new \ReflectionClass($class); + + if (str_contains($class, "\0")) { + $class = __CLASS__.'\\'.debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'].'_L'.$r->getStartLine(); + class_alias($r->name, $class); + } + $proxy = str_replace($r->name, $class, ProxyHelper::generateLazyGhost($r)); + $class = str_replace('\\', '_', $class).'_'.md5($proxy); + + if (!class_exists($class, false)) { + eval((\PHP_VERSION_ID >= 80200 && $r->isReadOnly() ? 'readonly ' : '').'class '.$class.' '.$proxy); + } + + return $class::createLazyGhost($initializer, $skippedProperties); + } } diff --git a/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php index 04497f02b34ae..074934ae17991 100644 --- a/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php +++ b/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php @@ -233,6 +233,18 @@ public function setFoo($foo): static $this->assertSame(234, $proxy->foo); } + public function testIndirectModification() + { + $obj = new class() { + public array $foo; + }; + $proxy = $this->createLazyProxy($obj::class, fn () => $obj); + + $proxy->foo[] = 123; + + $this->assertSame([123], $proxy->foo); + } + /** * @requires PHP 8.2 */ diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php index c5024ff4e32bf..205da6fd2c575 100644 --- a/src/Symfony/Component/Yaml/Dumper.php +++ b/src/Symfony/Component/Yaml/Dumper.php @@ -56,6 +56,8 @@ public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) { $output .= $prefix.Inline::dump($input, $flags); + } elseif ($input instanceof TaggedValue) { + $output .= $this->dumpTaggedValue($input, $inline, $indent, $flags, $prefix); } else { $dumpAsMap = Inline::isHash($input); @@ -135,4 +137,28 @@ public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags return $output; } + + private function dumpTaggedValue(TaggedValue $value, int $inline, int $indent, int $flags, string $prefix): string + { + $output = sprintf('%s!%s', $prefix ? $prefix.' ' : '', $value->getTag()); + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : ''; + $output .= sprintf(' |%s', $blockIndentationIndicator); + + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + + return $output; + } + + if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { + return $output.' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } + + return $output."\n".$this->dump($value->getValue(), $inline - 1, $indent, $flags); + } } diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index eedd3367d4e77..20308592f1c9b 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -440,6 +440,26 @@ public function testDumpingTaggedValueTopLevelAssocInline() $this->assertSameData($data, $this->parser->parse($yaml, Yaml::PARSE_CUSTOM_TAGS)); } + public function testDumpingTaggedValueTopLevelAssoc() + { + $data = new TaggedValue('user', ['name' => 'jane']); + + $expected = <<<'YAML' +!user +name: jane + +YAML; + $yaml = $this->dumper->dump($data, 2); + $this->assertSame($expected, $yaml); + } + + public function testDumpingTaggedValueTopLevelMultiLine() + { + $data = new TaggedValue('text', "a\nb\n"); + + $this->assertSame("!text |\n a\n b\n ", $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + } + public function testDumpingTaggedValueSpecialCharsInTag() { // @todo Validate the tag name in the TaggedValue constructor. diff --git a/src/Symfony/Contracts/Translation/Test/TranslatorTest.php b/src/Symfony/Contracts/Translation/Test/TranslatorTest.php index fbfa161929a4a..c5c37b355a5cb 100644 --- a/src/Symfony/Contracts/Translation/Test/TranslatorTest.php +++ b/src/Symfony/Contracts/Translation/Test/TranslatorTest.php @@ -137,7 +137,7 @@ public function getTransChoiceTests() } /** - * @dataProvider getInternal + * @dataProvider getInterval */ public function testInterval($expected, $number, $interval) { @@ -146,7 +146,7 @@ public function testInterval($expected, $number, $interval) $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number])); } - public function getInternal() + public function getInterval() { return [ ['foo', 3, '{1,2, 3 ,4}'],