8000 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 19e868793ac36..d925c20a4f318 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -42,6 +42,8 @@ /src/Symfony/Bundle/SecurityBundle/ @wouterj @chalasr /src/Symfony/Component/Security/ @wouterj @chalasr /src/Symfony/Component/Ldap/Security/ @wouterj @chalasr +# Scheduler +/src/Symfony/Component/Scheduler/ @kbond # TwigBundle /src/Symfony/Bundle/TwigBundle/ @yceruto # WebLink diff --git a/.github/expected-missing-return-types.diff b/.github/expected-missing-return-types.diff index 39683c93b38ba..8147714717550 100644 --- a/.github/expected-missing-return-types.diff +++ b/.github/expected-missing-return-types.diff @@ -3,1116 +3,14891 @@ sed -i 's/ *"\*\*\/Tests\/"//' composer.json composer u -o SYMFONY_PATCH_TYPE_DECLARATIONS='force=2&php=8.1' php .github/patch-types.php head=$(sed '/^diff /Q' .github/expected-missing-return-types.diff) +git checkout src/Symfony/Contracts/Service/ResetInterface.php (echo "$head" && echo && git diff -U2 src/) > .github/expected-missing-return-types.diff git checkout composer.json src/ -diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php -index bb5560a7b5..be86cbf98e 100644 ---- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php -+++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php -@@ -88,5 +88,5 @@ abstract class KernelTestCase extends TestCase - * @return Container +diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +index ada5fcbd49..51af652f08 100644 +--- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php ++++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +@@ -51,5 +51,5 @@ class DoctrineDataCollector extends DataCollector + * @return void */ -- protected static function getContainer(): ContainerInterface -+ protected static function getContainer(): Container +- public function addLogger(string $name, DebugStack $logger) ++ public function addLogger(string $name, DebugStack $logger): void { - if (!static::$booted) { -diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php -index 67355d9030..b2006ecd2f 100644 ---- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php -+++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php -@@ -449,5 +449,5 @@ class ProfilerControllerTest extends WebTestCase - * @return MockObject&DumpDataCollector + $this->loggers[$name] = $logger; +@@ -59,5 +59,5 @@ class DoctrineDataCollector extends DataCollector + * @return void */ -- private function createDumpDataCollector(): MockObject -+ private function createDumpDataCollector(): MockObject&DumpDataCollector +- public function collect(Request $request, Response $response, \Throwable $exception = null) ++ public function collect(Request $request, Response $response, \Throwable $exception = null): void { - $dumpDataCollector = $this->createMock(DumpDataCollector::class); -diff --git a/src/Symfony/Component/BrowserKit/AbstractBrowser.php b/src/Symfony/Component/BrowserKit/AbstractBrowser.php -index b27ca37529..5b80175850 100644 ---- a/src/Symfony/Component/BrowserKit/AbstractBrowser.php -+++ b/src/Symfony/Component/BrowserKit/AbstractBrowser.php -@@ -408,5 +408,5 @@ abstract class AbstractBrowser - * @throws \RuntimeException When processing returns exit code + $this->data = [ +@@ -90,5 +90,5 @@ class DoctrineDataCollector extends DataCollector + * @return void */ -- protected function doRequestInProcess(object $request) -+ protected function doRequestInProcess(object $request): object +- public function reset() ++ public function reset(): void { - $deprecationsFile = tempnam(sys_get_temp_dir(), 'deprec'); -@@ -441,5 +441,5 @@ abstract class AbstractBrowser - * @return object + $this->data = []; +@@ -119,5 +119,5 @@ class DoctrineDataCollector extends DataCollector + * @return int */ -- abstract protected function doRequest(object $request); -+ abstract protected function doRequest(object $request): object; - - /** -@@ -460,5 +460,5 @@ abstract class AbstractBrowser - * @return object +- public function getQueryCount() ++ public function getQueryCount(): int + { + return array_sum(array_map('count', $this->data['queries'])); +diff --git a/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php b/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php +index 4fa5057fe2..9e111adac5 100644 +--- a/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php ++++ b/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php +@@ -36,5 +36,5 @@ class ContainerAwareLoader extends Loader + * @return void */ -- protected function filterRequest(Request $request) -+ protected function filterRequest(Request $request): object +- public function addFixture(FixtureInterface $fixture) ++ public function addFixture(FixtureInterface $fixture): void { - return $request; -@@ -470,5 +470,5 @@ abstract class AbstractBrowser - * @return Response + if ($fixture instanceof ContainerAwareInterface) { +diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +index 1ce0ffd40c..585265fb38 100644 +--- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php ++++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +@@ -43,5 +43,5 @@ abstract class AbstractDoctrineExtension extends Extension + * @throws \InvalidArgumentException */ -- protected function filterResponse(object $response) -+ protected function filterResponse(object $response): Response +- protected function loadMappingInformation(array $objectManager, ContainerBuilder $container) ++ protected function loadMappingInformation(array $objectManager, ContainerBuilder $container): void { - return $response; -diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php -index 7cda0bc7d8..b2311826f4 100644 ---- a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php -+++ b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php -@@ -111,5 +111,5 @@ class NodeBuilder implements NodeParentInterface - * @return NodeDefinition&ParentNodeDefinitionInterface + if ($objectManager['auto_mapping']) { +@@ -111,5 +111,5 @@ abstract class AbstractDoctrineExtension extends Extension + * @return void */ -- public function end() -+ public function end(): NodeDefinition&ParentNodeDefinitionInterface +- protected function setMappingDriverAlias(array $mappingConfig, string $mappingName) ++ protected function setMappingDriverAlias(array $mappingConfig, string $mappingName): void { - return $this->parent; -diff --git a/src/Symfony/Component/Config/Definition/ConfigurationInterface.php b/src/Symfony/Component/Config/Definition/ConfigurationInterface.php -index 7b5d443fe6..d64ae0d024 100644 ---- a/src/Symfony/Component/Config/Definition/ConfigurationInterface.php -+++ b/src/Symfony/Component/Config/Definition/ConfigurationInterface.php -@@ -26,4 +26,4 @@ interface ConfigurationInterface - * @return TreeBuilder + if (isset($mappingConfig['alias'])) { +@@ -127,5 +127,5 @@ abstract class AbstractDoctrineExtension extends Extension + * @throws \InvalidArgumentException */ -- public function getConfigTreeBuilder(); -+ public function getConfigTreeBuilder(): TreeBuilder; - } -diff --git a/src/Symfony/Component/Config/FileLocator.php b/src/Symfony/Component/Config/FileLocator.php -index ab18232db1..bc5af7f89c 100644 ---- a/src/Symfony/Component/Config/FileLocator.php -+++ b/src/Symfony/Component/Config/FileLocator.php -@@ -31,5 +31,5 @@ class FileLocator implements FileLocatorInterface - } - -- public function locate(string $name, string $currentPath = null, bool $first = true) -+ public function locate(string $name, string $currentPath = null, bool $first = true): string|array +- protected function setMappingDriverConfig(array $mappingConfig, string $mappingName) ++ protected function setMappingDriverConfig(array $mappingConfig, string $mappingName): void { - if ('' === $name) { -diff --git a/src/Symfony/Component/Config/FileLocatorInterface.php b/src/Symfony/Component/Config/FileLocatorInterface.php -index e3ca1d49c4..526d350484 100644 ---- a/src/Symfony/Component/Config/FileLocatorInterface.php -+++ b/src/Symfony/Component/Config/FileLocatorInterface.php -@@ -31,4 +31,4 @@ interface FileLocatorInterface - * @throws FileLocatorFileNotFoundException If a file is not found + $mappingDirectory = $mappingConfig['dir']; +@@ -182,5 +182,5 @@ abstract class AbstractDoctrineExtension extends Extension + * @return void */ -- public function locate(string $name, string $currentPath = null, bool $first = true); -+ public function locate(string $name, string $currentPath = null, bool $first = true): string|array; - } -diff --git a/src/Symfony/Component/Config/Loader/FileLoader.php b/src/Symfony/Component/Config/Loader/FileLoader.php -index 30034e55a5..d6a57be190 100644 ---- a/src/Symfony/Component/Config/Loader/FileLoader.php -+++ b/src/Symfony/Component/Config/Loader/FileLoader.php -@@ -67,5 +67,5 @@ abstract class FileLoader extends Loader - * @throws FileLocatorFileNotFoundException +- protected function registerMappingDrivers(array $objectManager, ContainerBuilder $container) ++ protected function registerMappingDrivers(array $objectManager, ContainerBuilder $container): void + { + // configure metadata driver for each bundle based on the type of mapping files found +@@ -240,5 +240,5 @@ abstract class AbstractDoctrineExtension extends Extension + * @throws \InvalidArgumentException */ -- public function import(mixed $resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null, string|array $exclude = null) -+ public function import(mixed $resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null, string|array $exclude = null): mixed +- protected function assertValidMappingConfiguration(array $mappingConfig, string $objectManagerName) ++ protected function assertValidMappingConfiguration(array $mappingConfig, string $objectManagerName): void { - if (\is_string($resource) && \strlen($resource) !== ($i = strcspn($resource, '*?{[')) && !str_contains($resource, "\n")) { -diff --git a/src/Symfony/Component/Config/Loader/Loader.php b/src/Symfony/Component/Config/Loader/Loader.php -index 2ab50b021b..b64e1e1055 100644 ---- a/src/Symfony/Component/Config/Loader/Loader.php -+++ b/src/Symfony/Component/Config/Loader/Loader.php -@@ -44,5 +44,5 @@ abstract class Loader implements LoaderInterface - * @return mixed + if (!$mappingConfig['type'] || !$mappingConfig['dir'] || !$mappingConfig['prefix']) { +@@ -330,5 +330,5 @@ abstract class AbstractDoctrineExtension extends Extension + * @throws \InvalidArgumentException in case of unknown driver type */ -- public function import(mixed $resource, string $type = null) -+ public function import(mixed $resource, string $type = null): mixed +- protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, string $cacheName) ++ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, string $cacheName): void { - return $this->resolve($resource, $type)->load($resource, $type); -diff --git a/src/Symfony/Component/Config/Loader/LoaderInterface.php b/src/Symfony/Component/Config/Loader/LoaderInterface.php -index b94a4378f5..db502e12a7 100644 ---- a/src/Symfony/Component/Config/Loader/LoaderInterface.php -+++ b/src/Symfony/Component/Config/Loader/LoaderInterface.php -@@ -26,5 +26,5 @@ interface LoaderInterface - * @throws \Exception If something went wrong + $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName.'_driver'], $container); +diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php +index 83bfffaf27..acbd7e4bc7 100644 +--- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php ++++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php +@@ -32,5 +32,5 @@ class DoctrineValidationPass implements CompilerPassInterface + * @return void */ -- public function load(mixed $resource, string $type = null); -+ public function load(mixed $resource, string $type = null): mixed; +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $this->updateValidatorMappingFiles($container, 'xml', 'xml'); +diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +index 16a37b524a..24c2100206 100644 +--- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php ++++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +@@ -54,5 +54,5 @@ class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface + } - /** -@@ -35,5 +35,5 @@ interface LoaderInterface - * @return bool +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasParameter($this->connectionsParameter)) { +diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php +index 1a3f227c6d..daf6634922 100644 +--- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php ++++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php +@@ -134,5 +134,5 @@ abstract class RegisterMappingsPass implements CompilerPassInterface + * @return void */ -- public function supports(mixed $resource, string $type = null); -+ public function supports(mixed $resource, string $type = null): bool; - - /** -@@ -42,5 +42,5 @@ interface LoaderInterface - * @return LoaderResolverInterface +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$this->enabled($container)) { +diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php +index 80ee258438..e2c51954d0 100644 +--- a/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php ++++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php +@@ -37,5 +37,5 @@ class EntityFactory implements UserProviderFactoryInterface + * @return void */ -- public function getResolver(); -+ public function getResolver(): LoaderResolverInterface; - - /** -diff --git a/src/Symfony/Component/Config/ResourceCheckerInterface.php b/src/Symfony/Component/Config/ResourceCheckerInterface.php -index 6b1c6c5fbe..bb80ed461e 100644 ---- a/src/Symfony/Component/Config/ResourceCheckerInterface.php -+++ b/src/Symfony/Component/Config/ResourceCheckerInterface.php -@@ -33,5 +33,5 @@ interface ResourceCheckerInterface - * @return bool +- public function create(ContainerBuilder $container, string $id, array $config) ++ public function create(ContainerBuilder $container, string $id, array $config): void + { + $container +@@ -50,5 +50,5 @@ class EntityFactory implements UserProviderFactoryInterface + * @return string */ -- public function supports(ResourceInterface $metadata); -+ public function supports(ResourceInterface $metadata): bool; - - /** -@@ -42,4 +42,4 @@ interface ResourceCheckerInterface - * @return bool +- public function getKey() ++ public function getKey(): string + { + return $this->key; +@@ -58,5 +58,5 @@ class EntityFactory implements UserProviderFactoryInterface + * @return void */ -- public function isFresh(ResourceInterface $resource, int $timestamp); -+ public function isFresh(ResourceInterface $resource, int $timestamp): bool; - } -diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php -index d4ec1be090..747e093b1b 100644 ---- a/src/Symfony/Component/Console/Application.php -+++ b/src/Symfony/Component/Console/Application.php -@@ -215,5 +215,5 @@ class Application implements ResetInterface - * @return int 0 if everything went fine, or an error code +- public function addConfiguration(NodeDefinition $node) ++ public function addConfiguration(NodeDefinition $node): void + { + $node +diff --git a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php +index cff8b3b156..51e1f32e97 100644 +--- a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php ++++ b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php +@@ -42,5 +42,5 @@ class MergeDoctrineCollectionListener implements EventSubscriberInterface + * @return void */ -- public function doRun(InputInterface $input, OutputInterface $output) -+ public function doRun(InputInterface $input, OutputInterface $output): int +- public function onSubmit(FormEvent $event) ++ public function onSubmit(FormEvent $event): void { - if (true === $input->hasParameterOption(['--version', '-V'], true)) { -@@ -464,5 +464,5 @@ class Application implements ResetInterface - * @return string + $collection = $event->getForm()->getData(); +diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +index d1d72ef75a..68a437e8d7 100644 +--- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php ++++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +@@ -101,5 +101,5 @@ abstract class DoctrineType extends AbstractType implements ResetInterface + * @return void */ -- public function getLongVersion() -+ public function getLongVersion(): string +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void { - if ('UNKNOWN' !== $this->getName()) { -@@ -507,5 +507,5 @@ class Application implements ResetInterface - * @return Command|null + if ($options['multiple'] && interface_exists(Collection::class)) { +@@ -114,5 +114,5 @@ abstract class DoctrineType extends AbstractType implements ResetInterface + * @return void */ -- public function add(Command $command) -+ public function add(Command $command): ?Command +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void { - $this->init(); -@@ -544,5 +544,5 @@ class Application implements ResetInterface - * @throws CommandNotFoundException When given command name does not exist + $choiceLoader = function (Options $options) { +@@ -242,5 +242,5 @@ abstract class DoctrineType extends AbstractType implements ResetInterface + * @return void */ -- public function get(string $name) -+ public function get(string $name): Command +- public function reset() ++ public function reset(): void { - $this->init(); -@@ -651,5 +651,5 @@ class Application implements ResetInterface - * @throws CommandNotFoundException When command name is incorrect or ambiguous + $this->idReaders = []; +diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +index c096b558db..8d584900a9 100644 +--- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php ++++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +@@ -25,5 +25,5 @@ class EntityType extends DoctrineType + * @return void */ -- public function find(string $name) -+ public function find(string $name): Command +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void { - $this->init(); -@@ -761,5 +761,5 @@ class Application implements ResetInterface - * @return Command[] + parent::configureOptions($resolver); +diff --git a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +index b2369e95d6..c33484608e 100644 +--- a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php ++++ b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +@@ -52,5 +52,5 @@ class DbalLogger implements SQLLogger + * @return void */ -- public function all(string $namespace = null) -+ public function all(string $namespace = null): array +- protected function log(string $message, array $params) ++ protected function log(string $message, array $params): void { - $this->init(); -@@ -970,5 +970,5 @@ class Application implements ResetInterface - * @return int 0 if everything went fine, or an error code + $this->logger->debug($message, $params); +diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php +index 38618fc15e..eb599eb0b4 100644 +--- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php ++++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php +@@ -34,5 +34,5 @@ class DoctrineClearEntityManagerWorkerSubscriber implements EventSubscriberInter + * @return void */ -- protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) -+ protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int +- public function onWorkerMessageHandled() ++ public function onWorkerMessageHandled(): void { - foreach ($command->getHelperSet() as $helper) { -diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php -index 1e3c1a5a2b..923ab29a4d 100644 ---- a/src/Symfony/Component/Console/Command/Command.php -+++ b/src/Symfony/Component/Console/Command/Command.php -@@ -192,5 +192,5 @@ class Command - * @return bool + $this->clearEntityManagers(); +@@ -42,5 +42,5 @@ class DoctrineClearEntityManagerWorkerSubscriber implements EventSubscriberInter + * @return void */ -- public function isEnabled() -+ public function isEnabled(): bool +- public function onWorkerMessageFailed() ++ public function onWorkerMessageFailed(): void { - return true; -@@ -218,5 +218,5 @@ class Command - * @see setCode() + $this->clearEntityManagers(); +diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +index 9d61be61bd..e89985de26 100644 +--- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php ++++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +@@ -70,5 +70,5 @@ class DoctrineTokenProvider implements TokenProviderInterface, TokenVerifierInte + * @return void */ -- protected function execute(InputInterface $input, OutputInterface $output) -+ protected function execute(InputInterface $input, OutputInterface $output): int +- public function deleteTokenBySeries(string $series) ++ public function deleteTokenBySeries(string $series): void { - throw new LogicException('You must override the execute() method in the concrete command class.'); -@@ -687,5 +687,5 @@ class Command - * @throws InvalidArgumentException if the helper is not defined + $sql = 'DELETE FROM rememberme_token WHERE series=:series'; +@@ -85,5 +85,5 @@ class DoctrineTokenProvider implements TokenProviderInterface, TokenVerifierInte + * @return void */ -- public function getHelper(string $name): mixed -+ public function getHelper(string $name): HelperInterface +- public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTime $lastUsed) ++ public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTime $lastUsed): void { - if (null === $this->helperSet) { -diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatter.php b/src/Symfony/Component/Console/Formatter/OutputFormatter.php -index 38e75c3178..e5db4d626e 100644 ---- a/src/Symfony/Component/Console/Formatter/OutputFormatter.php -+++ b/src/Symfony/Component/Console/Formatter/OutputFormatter.php -@@ -116,5 +116,5 @@ class OutputFormatter implements WrappableOutputFormatterInterface - } - -- public function formatAndWrap(?string $message, int $width) -+ public function formatAndWrap(?string $message, int $width): string + $sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed WHERE series=:series'; +@@ -111,5 +111,5 @@ class DoctrineTokenProvider implements TokenProviderInterface, TokenVerifierInte + * @return void + */ +- public function createNewToken(PersistentTokenInterface $token) ++ public function createNewToken(PersistentTokenInterface $token): void { - if (null === $message) { -diff --git a/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php b/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php -index 746cd27e79..52c61429cf 100644 ---- a/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php -+++ b/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php -@@ -24,4 +24,4 @@ interface WrappableOutputFormatterInterface extends OutputFormatterInterface - * @return string + $sql = 'INSERT INTO rememberme_token (class, username, series, value, lastUsed) VALUES (:class, :username, :series, :value, :lastUsed)'; +diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +index 67575134b6..09a3926120 100644 +--- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php ++++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +@@ -43,5 +43,5 @@ class UniqueEntityValidator extends ConstraintValidator + * @throws ConstraintDefinitionException */ -- public function formatAndWrap(?string $message, int $width); -+ public function formatAndWrap(?string $message, int $width): string; - } -diff --git a/src/Symfony/Component/Console/Helper/HelperInterface.php b/src/Symfony/Component/Console/Helper/HelperInterface.php -index 2762cdf05c..737334268a 100644 ---- a/src/Symfony/Component/Console/Helper/HelperInterface.php -+++ b/src/Symfony/Component/Console/Helper/HelperInterface.php -@@ -34,4 +34,4 @@ interface HelperInterface - * @return string +- public function validate(mixed $entity, Constraint $constraint) ++ public function validate(mixed $entity, Constraint $constraint): void + { + if (!$constraint instanceof UniqueEntity) { +diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php +index bf8a5feb9f..e346c8b17c 100644 +--- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php ++++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php +@@ -32,5 +32,5 @@ class DoctrineInitializer implements ObjectInitializerInterface + * @return void */ -- public function getName(); -+ public function getName(): string; - } -diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php -index 893b3192e9..94822ed191 100644 ---- a/src/Symfony/Component/Console/Helper/Table.php -+++ b/src/Symfony/Component/Console/Helper/Table.php -@@ -193,5 +193,5 @@ class Table - * @return $this +- public function initialize(object $object) ++ public function initialize(object $object): void + { + $this->registry->getManagerForClass($object::class)?->initializeObject($object); +diff --git a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php +index 5210e8eefa..0e842abb76 100644 +--- a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php ++++ b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php +@@ -54,5 +54,5 @@ class ServerLogCommand extends Command + * @return void */ -- public function setRows(array $rows) -+ public function setRows(array $rows): static +- protected function configure() ++ protected function configure(): void { - $this->rows = []; -diff --git a/src/Symfony/Component/Console/Input/InputInterface.php b/src/Symfony/Component/Console/Input/InputInterface.php -index 3af991a76f..742e2508f3 100644 ---- a/src/Symfony/Component/Console/Input/InputInterface.php -+++ b/src/Symfony/Component/Console/Input/InputInterface.php -@@ -57,5 +57,5 @@ interface InputInterface - * @return mixed + if (!class_exists(ConsoleFormatter::class)) { +diff --git a/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php +index 718be59c13..091f24a8f8 100644 +--- a/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php ++++ b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php +@@ -81,5 +81,5 @@ class MailerHandler extends AbstractProcessingHandler + * @return void */ -- public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false); -+ public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed; - - /** -@@ -87,5 +87,5 @@ interface InputInterface - * @throws InvalidArgumentException When argument given doesn't exist +- protected function send(string $content, array $records) ++ protected function send(string $content, array $records): void + { + $this->mailer->send($this->buildMessage($content, $records)); +diff --git a/src/Symfony/Bridge/Monolog/Logger.php b/src/Symfony/Bridge/Monolog/Logger.php +index 367b3351ff..fb603e74ba 100644 +--- a/src/Symfony/Bridge/Monolog/Logger.php ++++ b/src/Symfony/Bridge/Monolog/Logger.php +@@ -44,5 +44,5 @@ class Logger extends BaseLogger implements DebugLoggerInterface, ResetInterface + * @return void */ -- public function getArgument(string $name); -+ public function getArgument(string $name): mixed; - - /** -@@ -115,5 +115,5 @@ interface InputInterface - * @throws InvalidArgumentException When option given doesn't exist +- public function clear() ++ public function clear(): void + { + if ($logger = $this->getDebugLogger()) { +@@ -63,5 +63,5 @@ class Logger extends BaseLogger implements DebugLoggerInterface, ResetInterface + * @return void */ -- public function getOption(string $name); -+ public function getOption(string $name): mixed; - - /** -diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler 8000 /AbstractRecursivePass.php -index 08bab02ee4..1181f0795e 100644 ---- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php -+++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php -@@ -68,5 +68,5 @@ abstract class AbstractRecursivePass implements CompilerPassInterface - * @return mixed +- public function removeDebugLogger() ++ public function removeDebugLogger(): void + { + foreach ($this->processors as $k => $processor) { +diff --git a/src/Symfony/Bridge/Monolog/Processor/ConsoleCommandProcessor.php b/src/Symfony/Bridge/Monolog/Processor/ConsoleCommandProcessor.php +index df2a718720..2ccab3649f 100644 +--- a/src/Symfony/Bridge/Monolog/Processor/ConsoleCommandProcessor.php ++++ b/src/Symfony/Bridge/Monolog/Processor/ConsoleCommandProcessor.php +@@ -51,5 +51,5 @@ class ConsoleCommandProcessor implements EventSubscriberInterface, ResetInterfac + * @return void */ -- protected function processValue(mixed $value, bool $isRoot = false) -+ protected function processValue(mixed $value, bool $isRoot = false): mixed +- public function reset() ++ public function reset(): void { - if (\is_array($value)) { -diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php -index f5d33682ff..e644489097 100644 ---- a/src/Symfony/Component/DependencyInjection/Container.php -+++ b/src/Symfony/Component/DependencyInjection/Container.php -@@ -110,5 +110,5 @@ class Container implements ContainerInterface, ResetInterface - * @throws ParameterNotFoundException if the parameter is not defined + unset($this->commandData); +@@ -59,5 +59,5 @@ class ConsoleCommandProcessor implements EventSubscriberInterface, ResetInterfac + * @return void */ -- public function getParameter(string $name) -+ public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null +- public function addCommandData(ConsoleEvent $event) ++ public function addCommandData(ConsoleEvent $event): void { - return $this->parameterBag->get($name); -diff --git a/src/Symfony/Component/DependencyInjection/ContainerInterface.php b/src/Symfony/Component/DependencyInjection/ContainerInterface.php -index 9e97fb71fc..1cda97c611 100644 ---- a/src/Symfony/Component/DependencyInjection/ContainerInterface.php -+++ b/src/Symfony/Component/DependencyInjection/ContainerInterface.php -@@ -53,5 +53,5 @@ interface ContainerInterface extends PsrContainerInterface - * @throws ParameterNotFoundException if the parameter is not defined + $this->commandData = [ +diff --git a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php +index c1ce2898da..7a9bf04e18 100644 +--- a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php ++++ b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php +@@ -94,5 +94,5 @@ class DebugProcessor implements DebugLoggerInterface, ResetInterface + * @return void */ -- public function getParameter(string $name); -+ public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null; - - public function hasParameter(string $name): bool; -diff --git a/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php b/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php -index a42967f4da..4e86e16f9d 100644 ---- a/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php -+++ b/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php -@@ -27,4 +27,4 @@ interface ConfigurationExtensionInterface - * @return ConfigurationInterface|null +- public function clear() ++ public function clear(): void + { + $this->records = []; +@@ -103,5 +103,5 @@ class DebugProcessor implements DebugLoggerInterface, ResetInterface + * @return void */ -- public function getConfiguration(array $config, ContainerBuilder $container); -+ public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface; - } -diff --git a/src/Symfony/Component/DependencyInjection/Extension/Extension.php b/src/Symfony/Component/DependencyInjection/Extension/Extension.php -index 00192d0da5..620efa4fd1 100644 ---- a/src/Symfony/Component/DependencyInjection/Extension/Extension.php -+++ b/src/Symfony/Component/DependencyInjection/Extension/Extension.php -@@ -29,10 +29,10 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn - private array $processedConfigs = []; - -- public function getXsdValidationBasePath() -+ public function getXsdValidationBasePath(): string|false +- public function reset() ++ public function reset(): void { - return false; - } - -- public function getNamespace() -+ public function getNamespace(): string + $this->clear(); +diff --git a/src/Symfony/Bridge/Twig/AppVariable.php b/src/Symfony/Bridge/Twig/AppVariable.php +index 8bfaa0a22c..745ba6aff3 100644 +--- a/src/Symfony/Bridge/Twig/AppVariable.php ++++ b/src/Symfony/Bridge/Twig/AppVariable.php +@@ -36,5 +36,5 @@ class AppVariable + * @return void + */ +- public function setTokenStorage(TokenStorageInterface $tokenStorage) ++ public function setTokenStorage(TokenStorageInterface $tokenStorage): void { - return 'http://example.org/schema/dic/'.$this->getAlias(); -@@ -68,5 +68,5 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn - } - -- public function getConfiguration(array $config, ContainerBuilder $container) -+ public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface + $this->tokenStorage = $tokenStorage; +@@ -44,5 +44,5 @@ class AppVariable + * @return void + */ +- public function setRequestStack(RequestStack $requestStack) ++ public function setRequestStack(RequestStack $requestStack): void { - $class = static::class; -diff --git a/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php b/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php -index 11cda00cc5..07b4990160 100644 ---- a/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php -+++ b/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php -@@ -35,5 +35,5 @@ interface ExtensionInterface - * @return string + $this->requestStack = $requestStack; +@@ -52,5 +52,5 @@ class AppVariable + * @return void */ -- public function getNamespace(); -+ public function getNamespace(): string; - - /** -@@ -42,5 +42,5 @@ interface ExtensionInterface - * @return string|false +- public function setEnvironment(string $environment) ++ public function setEnvironment(string $environment): void + { + $this->environment = $environment; +@@ -60,5 +60,5 @@ class AppVariable + * @return void */ -- public function getXsdValidationBasePath(); -+ public function getXsdValidationBasePath(): string|false; - - /** -@@ -51,4 +51,4 @@ interface ExtensionInterface +- public function setDebug(bool $debug) ++ public function setDebug(bool $debug): void + { + $this->debug = $debug; +diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php +index 43e4d9c9f1..ea0116870b 100644 +--- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php ++++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php +@@ -63,5 +63,5 @@ class DebugCommand extends Command + * @return void + */ +- protected function configure() ++ protected function configure(): void + { + $this +diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php +index e059740a13..e7d6fbdd05 100644 +--- a/src/Symfony/Bridge/Twig/Command/LintCommand.php ++++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php +@@ -52,5 +52,5 @@ class LintCommand extends Command + * @return void + */ +- protected function configure() ++ protected function configure(): void + { + $this +diff --git a/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php b/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php +index ef0f9ba954..16240deb4a 100644 +--- a/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php ++++ b/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php +@@ -32,5 +32,5 @@ class TemplateAttributeListener implements EventSubscriberInterface + * @return void + */ +- public function onKernelView(ViewEvent $event) ++ public function onKernelView(ViewEvent $event): void + { + $parameters = $event->getControllerResult(); +diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php +index fbe8e0b3e4..6e7fefc70d 100644 +--- a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php ++++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php +@@ -136,5 +136,5 @@ class TwigRendererEngine extends AbstractRendererEngine + * @return void + */ +- protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme) ++ protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme): void + { + if (!$theme instanceof Template) { +diff --git a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php +index 2b44c5ef8d..05b4428490 100644 +--- a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php ++++ b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php +@@ -49,5 +49,5 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface + * @return void + */ +- public function extract($resource, MessageCatalogue $catalogue) ++ public function extract($resource, MessageCatalogue $catalogue): void + { + foreach ($this->extractFiles($resource) as $file) { +@@ -63,5 +63,5 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface + * @return void + */ +- public function setPrefix(string $prefix) ++ public function setPrefix(string $prefix): void + { + $this->prefix = $prefix; +@@ -71,5 +71,5 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface + * @return void + */ +- protected function extractTemplate(string $template, MessageCatalogue $catalogue) ++ protected function extractTemplate(string $template, MessageCatalogue $catalogue): void + { + $visitor = $this->twig->getExtension(TranslationExtension::class)->getTranslationNodeVisitor(); +diff --git a/src/Symfony/Bundle/DebugBundle/DebugBundle.php b/src/Symfony/Bundle/DebugBundle/DebugBundle.php +index a24321e229..1e9e893142 100644 +--- a/src/Symfony/Bundle/DebugBundle/DebugBundle.php ++++ b/src/Symfony/Bundle/DebugBundle/DebugBundle.php +@@ -26,5 +26,5 @@ class DebugBundle extends Bundle + * @return void + */ +- public function boot() ++ public function boot(): void + { + if ($this->container->getParameter('kernel.debug')) { +@@ -51,5 +51,5 @@ class DebugBundle extends Bundle + * @return void + */ +- public function build(ContainerBuilder $container) ++ public function build(ContainerBuilder $container): void + { + parent::build($container); +@@ -61,5 +61,5 @@ class DebugBundle extends Bundle + * @return void + */ +- public function registerCommands(Application $application) ++ public function registerCommands(Application $application): void + { + // noop +diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php +index cecce87c4a..7c6d738481 100644 +--- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php ++++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php +@@ -26,5 +26,5 @@ class DumpDataCollectorPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('data_collector.dump')) { +diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php +index eadeafba55..4f1ca3bfef 100644 +--- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php ++++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php +@@ -33,5 +33,5 @@ class DebugExtension extends Extension + * @return void + */ +- public function load(array $configs, ContainerBuilder $container) ++ public function load(array $configs, ContainerBuilder $container): void + { + $configuration = new Configuration(); +diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +index cdbb90a3a9..b137a12012 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php ++++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +@@ -56,5 +56,5 @@ class Application extends BaseApplication + * @return void + */ +- public function reset() ++ public function reset(): void + { + if ($this->kernel->getContainer()->has('services_resetter')) { +@@ -144,5 +144,5 @@ class Application extends BaseApplication + * @return void + */ +- protected function registerCommands() ++ protected function registerCommands(): void + { + if ($this->commandsRegistered) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DataCollector/RouterDataCollector.php b/src/Symfony/Bundle/FrameworkBundle/DataCollector/RouterDataCollector.php +index ccb61b1286..700d0f22d4 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DataCollector/RouterDataCollector.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DataCollector/RouterDataCollector.php +@@ -23,5 +23,5 @@ use Symfony\Component\HttpKernel\DataCollector\RouterDataCollector as BaseRouter + class RouterDataCollector extends BaseRouterDataCollector + { +- public function guessRoute(Request $request, mixed $controller) ++ public function guessRoute(Request $request, mixed $controller): string + { + if (\is_array($controller)) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php +index d0aca7a06b..aee4af1a43 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php +@@ -21,5 +21,5 @@ class AddDebugLogProcessorPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('profiler')) { +@@ -41,5 +41,5 @@ class AddDebugLogProcessorPass implements CompilerPassInterface + * @return void + */ +- public static function configureLogger(mixed $logger) ++ public static function configureLogger(mixed $logger): void + { + if (\is_object($logger) && method_exists($logger, 'removeDebugLogger') && \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +index 5442b27734..9ed6cf284c 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +@@ -26,5 +26,5 @@ class AddExpressionLanguageProvidersPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + // routing +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AssetsContextPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AssetsContextPass.php +index e8c2ad3a0e..d8b3b64f5d 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AssetsContextPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AssetsContextPass.php +@@ -22,5 +22,5 @@ class AssetsContextPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('assets.context')) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php +index 1e08ef3149..530bbdc4cd 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php +@@ -29,5 +29,5 @@ class ContainerBuilderDebugDumpPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->getParameter('debug.container.dump')) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php +index e66e98b451..7714d62f3e 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php +@@ -24,5 +24,5 @@ class DataCollectorTranslatorPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->has('translator')) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php +index b7cb920bf7..76b5dace0f 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php +@@ -26,5 +26,5 @@ class LoggingTranslatorPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasAlias('logger') || !$container->hasAlias('translator')) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ProfilerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ProfilerPass.php +index c2d669fe1c..322f3eaeee 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ProfilerPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ProfilerPass.php +@@ -28,5 +28,5 @@ class ProfilerPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (false === $container->hasDefinition('profiler')) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php +index fedc30d06e..bb934fe1f6 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php +@@ -23,5 +23,5 @@ class RemoveUnusedSessionMarshallingHandlerPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('session.marshalling_handler')) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php +index 09f272daa9..1fd0608bde 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php +@@ -25,5 +25,5 @@ class TestServiceContainerRealRefPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('test.private_services_locator')) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php +index 6e7669a710..27517d34ae 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php +@@ -25,5 +25,5 @@ class TestServiceContainerWeakRefPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('test.private_services_locator')) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +index 9da5b91bb3..d7acf8040b 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +@@ -108,5 +108,5 @@ class UnusedTagsPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $tags = array_unique(array_merge($container->findTags(), self::KNOWN_TAGS)); +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php +index bda9ca9515..c0d1f91339 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php +@@ -25,5 +25,5 @@ class WorkflowGuardListenerPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasParameter('workflow.has_guard_listeners')) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +index 36622ee23d..cd6a609053 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php ++++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +@@ -206,5 +206,5 @@ class FrameworkExtension extends Extension + * @throws LogicException + */ +- public function load(array $configs, ContainerBuilder $container) ++ public function load(array $configs, ContainerBuilder $container): void + { + $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config')); +@@ -2795,5 +2795,5 @@ class FrameworkExtension extends Extension + * @return void + */ +- public static function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig) ++ public static function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig): void + { + trigger_deprecation('symfony/framework-bundle', '6.2', 'The "%s()" method is deprecated.', __METHOD__); +diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +index ffb96a23e5..e7f77881c8 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php ++++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +@@ -94,5 +94,5 @@ class FrameworkBundle extends Bundle + * @return void + */ +- public function boot() ++ public function boot(): void + { + $handler = ErrorHandler::register(null, false); +@@ -111,5 +111,5 @@ class FrameworkBundle extends Bundle + * @return void + */ +- public function build(ContainerBuilder $container) ++ public function build(ContainerBuilder $container): void + { + parent::build($container); +diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +index 3ab28a1f81..2ace764149 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php ++++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +@@ -132,5 +132,5 @@ trait MicroKernelTrait + * @return void + */ +- public function registerContainerConfiguration(LoaderInterface $loader) ++ public function registerContainerConfiguration(LoaderInterface $loader): void + { + $loader->load(function (ContainerBuilder $container) use ($loader) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +index 0379be8f5b..568f30f72d 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php ++++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +@@ -77,5 +77,5 @@ class KernelBrowser extends HttpKernelBrowser + * @return void + */ +- public function enableProfiler() ++ public function enableProfiler(): void + { + if ($this->getContainer()->has('profiler')) { +@@ -92,5 +92,5 @@ class KernelBrowser extends HttpKernelBrowser + * @return void + */ +- public function disableReboot() ++ public function disableReboot(): void + { + $this->reboot = false; +@@ -102,5 +102,5 @@ class KernelBrowser extends HttpKernelBrowser + * @return void + */ +- public function enableReboot() ++ public function enableReboot(): void + { + $this->reboot = true; +diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php +index ec03f84979..498dc24d1c 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php ++++ b/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php +@@ -28,5 +28,5 @@ class AnnotatedRouteControllerLoader extends AnnotationClassLoader + * @return void + */ +- protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot) ++ protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void + { + if ('__invoke' === $method->getName()) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php +index b3eb0c6bc3..c47152f219 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php ++++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php +@@ -44,5 +44,5 @@ abstract class AbstractVault * @return string */ -- public function getAlias(); -+ public function getAlias(): string; - } -diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php -index f4c6b29258..1402331f9e 100644 ---- a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php -+++ b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php -@@ -31,4 +31,4 @@ interface InstantiatorInterface - * @return object +- protected function getPrettyPath(string $path) ++ protected function getPrettyPath(string $path): string + { + return str_replace(getcwd().\DIRECTORY_SEPARATOR, '', $path); +diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +index bb5560a7b5..be86cbf98e 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php ++++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +@@ -88,5 +88,5 @@ abstract class KernelTestCase extends TestCase + * @return Container */ -- public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator); -+ public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object; - } -diff --git a/src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php b/src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php -index 2085e428e9..ca0d6964e5 100644 ---- a/src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php -+++ b/src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php -@@ -46,4 +46,4 @@ interface EventSubscriberInterface - * @return array> +- protected static function getContainer(): ContainerInterface ++ protected static function getContainer(): Container + { + if (!static::$booted) { +diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +index dac3b6394f..d319cb0824 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php ++++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +@@ -122,5 +122,5 @@ class Translator extends BaseTranslator implements WarmableInterface + * @return void */ -- public static function getSubscribedEvents(); -+ public static function getSubscribedEvents(): array; - } -diff --git a/src/Symfony/Component/ExpressionLanguage/ExpressionFunctionProviderInterface.php b/src/Symfony/Component/ExpressionLanguage/ExpressionFunctionProviderInterface.php -index 479aeef880..272954c082 100644 ---- a/src/Symfony/Component/ExpressionLanguage/ExpressionFunctionProviderInterface.php -+++ b/src/Symfony/Component/ExpressionLanguage/ExpressionFunctionProviderInterface.php -@@ -20,4 +20,4 @@ interface ExpressionFunctionProviderInterface - * @return ExpressionFunction[] +- public function addResource(string $format, mixed $resource, string $locale, string $domain = null) ++ public function addResource(string $format, mixed $resource, string $locale, string $domain = null): void + { + if ($this->resourceFiles) { +@@ -133,5 +133,5 @@ class Translator extends BaseTranslator implements WarmableInterface + * @return void */ -- public function getFunctions(); -+ public function getFunctions(): array; - } -diff --git a/src/Symfony/Component/Form/AbstractExtension.php b/src/Symfony/Componen 8000 t/Form/AbstractExtension.php -index 5d3c1a8480..7eb6bba474 100644 ---- a/src/Symfony/Component/Form/AbstractExtension.php -+++ b/src/Symfony/Component/Form/AbstractExtension.php -@@ -99,5 +99,5 @@ abstract class AbstractExtension implements FormExtensionInterface - * @return FormTypeInterface[] +- protected function initializeCatalogue(string $locale) ++ protected function initializeCatalogue(string $locale): void + { + $this->initialize(); +@@ -155,5 +155,5 @@ class Translator extends BaseTranslator implements WarmableInterface + * @return void */ -- protected function loadTypes() -+ protected function loadTypes(): array +- protected function initialize() ++ protected function initialize(): void { - return []; -@@ -119,5 +119,5 @@ abstract class AbstractExtension implements FormExtensionInterface - * @return FormTypeGuesserInterface|null + if ($this->resourceFiles) { +diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php +index bb5fe03096..e756cd7957 100644 +--- a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php ++++ b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php +@@ -32,5 +32,5 @@ final class TraceableFirewallListener extends FirewallListener + * @return array */ -- protected function loadTypeGuesser() -+ protected function loadTypeGuesser(): ?FormTypeGuesserInterface +- public function getWrappedListeners() ++ public function getWrappedListeners(): array { - return null; -diff --git a/src/Symfony/Component/Form/AbstractRendererEngine.php b/src/Symfony/Component/Form/AbstractRendererEngine.php -index f79f1c1338..c74c4bf2f1 100644 ---- a/src/Symfony/Component/Form/AbstractRendererEngine.php -+++ b/src/Symfony/Component/Form/AbstractRendererEngine.php -@@ -125,5 +125,5 @@ abstract class AbstractRendererEngine implements FormRendererEngineInterface, Re - * @return bool + return $this->wrappedListeners; +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +index 08d7fd9213..8bacecce11 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +@@ -26,5 +26,5 @@ class AddExpressionLanguageProvidersPass implements CompilerPassInterface + * @return void */ -- abstract protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName); -+ abstract protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName): bool; - - /** -diff --git a/src/Symfony/Component/Form/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php -index da401930d7..15d6219259 100644 ---- a/src/Symfony/Component/Form/AbstractType.php -+++ b/src/Symfony/Component/Form/AbstractType.php -@@ -37,10 +37,10 @@ abstract class AbstractType implements FormTypeInterface - } - -- public function getBlockPrefix() -+ public function getBlockPrefix(): string +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void { - return StringUtil::fqcnToBlockPrefix(static::class) ?: ''; - } - -- public function getParent() -+ public function getParent(): ?string + if ($container->has('security.expression_language')) { +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php +index ccf474087a..f9e4b0622f 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php +@@ -33,5 +33,5 @@ class AddSecurityVotersPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void { - return FormType::class; -diff --git a/src/Symfony/Component/Form/DataTransformerInterface.php b/src/Symfony/Component/Form/DataTransformerInterface.php -index 85fb99d218..6cc654f681 100644 ---- a/src/Symfony/Component/Form/DataTransformerInterface.php -+++ b/src/Symfony/Component/Form/DataTransformerInterface.php -@@ -65,5 +65,5 @@ interface DataTransformerInterface - * @throws TransformationFailedException when the transformation fails + if (!$container->hasDefinition('security.access.decision_manager')) { +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php +index 9a7a94ca08..bc4baa0f65 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php +@@ -25,5 +25,5 @@ class AddSessionDomainConstraintPass implements CompilerPassInterface + * @return void */ -- public function transform(mixed $value); -+ public function transform(mixed $value): mixed; +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasParameter('session.storage.options') || !$container->has('security.http_utils')) { +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php +index 2041a36b38..d54311d5de 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php +@@ -25,5 +25,5 @@ class CleanRememberMeVerifierPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('cache.system')) { +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php +index e7c77d1ec3..419e18f6f0 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php +@@ -26,5 +26,5 @@ class MakeFirewallsEventDispatcherTraceablePass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) { +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php +index 3ca2a70acb..2ba4a41499 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php +@@ -27,5 +27,5 @@ class RegisterEntryPointPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasParameter('security.firewalls')) { +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php +index 24eb1377c5..6367585643 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php +@@ -56,5 +56,5 @@ abstract class AbstractFactory implements AuthenticatorFactoryInterface + * @return void + */ +- public function addConfiguration(NodeDefinition $node) ++ public function addConfiguration(NodeDefinition $node): void + { + $builder = $node->children(); +@@ -79,5 +79,5 @@ abstract class AbstractFactory implements AuthenticatorFactoryInterface + * @return string + */ +- protected function createAuthenticationSuccessHandler(ContainerBuilder $container, string $id, array $config) ++ protected function createAuthenticationSuccessHandler(ContainerBuilder $container, string $id, array $config): string + { + $successHandlerId = $this->getSuccessHandlerId($id); +@@ -101,5 +101,5 @@ abstract class AbstractFactory implements AuthenticatorFactoryInterface + * @return string + */ +- protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config) ++ protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config): string + { + $id = $this->getFailureHandlerId($id); +@@ -121,5 +121,5 @@ abstract class AbstractFactory implements AuthenticatorFactoryInterface + * @return string + */ +- protected function getSuccessHandlerId(string $id) ++ protected function getSuccessHandlerId(string $id): string + { + return 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey()); +@@ -129,5 +129,5 @@ abstract class AbstractFactory implements AuthenticatorFactoryInterface + * @return string + */ +- protected function getFailureHandlerId(string $id) ++ protected function getFailureHandlerId(string $id): string + { + return 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey()); +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php +index 8082b6f352..bd9e1cff26 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php +@@ -34,5 +34,5 @@ interface AuthenticatorFactoryInterface + * @return void + */ +- public function addConfiguration(NodeDefinition $builder); ++ public function addConfiguration(NodeDefinition $builder): void; /** -@@ -96,4 +96,4 @@ interface DataTransformerInterface - * @throws TransformationFailedException when the transformation fails +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php +index 936f58a084..1a3c89381b 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php +@@ -28,5 +28,5 @@ class InMemoryFactory implements UserProviderFactoryInterface + * @return void */ -- public function reverseTransform(mixed $value); -+ public function reverseTransform(mixed $value): mixed; - } -diff --git a/src/Symfony/Component/Form/FormRendererEngineInterface.php b/src/Symfony/Component/Form/FormRendererEngineInterface.php -index aa249270a0..3c9d04ff9a 100644 ---- a/src/Symfony/Component/Form/FormRendererEngineInterface.php -+++ b/src/Symfony/Component/Form/FormRendererEngineInterface.php -@@ -131,4 +131,4 @@ interface FormRendererEngineInterface +- public function create(ContainerBuilder $container, string $id, array $config) ++ public function create(ContainerBuilder $container, string $id, array $config): void + { + $definition = $container->setDefinition($id, new ChildDefinition('security.user.provider.in_memory')); +@@ -44,5 +44,5 @@ class InMemoryFactory implements UserProviderFactoryInterface * @return string */ -- public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []); -+ public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []): string; - } -diff --git a/src/Symfony/Component/Form/FormTypeGuesserInterface.php b/src/Symfony/Component/Form/FormTypeGuesserInterface.php -index 61e2c5f80d..4d6b335474 100644 ---- a/src/Symfony/Component/Form/FormTypeGuesserInterface.php -+++ b/src/Symfony/Component/Form/FormTypeGuesserInterface.php -@@ -22,5 +22,5 @@ interface FormTypeGuesserInterface - * @return Guess\TypeGuess|null +- public function getKey() ++ public function getKey(): string + { + return 'memory'; +@@ -52,5 +52,5 @@ class InMemoryFactory implements UserProviderFactoryInterface + * @return void */ -- public function guessType(string $class, string $property); -+ public function guessType(string $class, string $property): ?Guess\TypeGuess; +- public function addConfiguration(NodeDefinition $node) ++ public function addConfiguration(NodeDefinition $node): void + { + $node +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php +index 2f4dca01d1..ca99ad286f 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php +@@ -28,5 +28,5 @@ class LdapFactory implements UserProviderFactoryInterface + * @return void + */ +- public function create(ContainerBuilder $container, string $id, array $config) ++ public function create(ContainerBuilder $container, string $id, array $config): void + { + $container +@@ -47,5 +47,5 @@ class LdapFactory implements UserProviderFactoryInterface + * @return string + */ +- public function getKey() ++ public function getKey(): string + { + return 'ldap'; +@@ -55,5 +55,5 @@ class LdapFactory implements UserProviderFactoryInterface + * @return void + */ +- public function addConfiguration(NodeDefinition $node) ++ public function addConfiguration(NodeDefinition $node): void + { + $node +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php +index a2c5815e4b..1c9721ccc6 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php +@@ -26,14 +26,14 @@ interface UserProviderFactoryInterface + * @return void + */ +- public function create(ContainerBuilder $container, string $id, array $config); ++ public function create(ContainerBuilder $container, string $id, array $config): void; /** -@@ -29,5 +29,5 @@ interface FormTypeGuesserInterface - * @return Guess\ValueGuess|null + * @return string */ -- public function guessRequired(string $class, string $property); -+ public function guessRequired(string $class, string $property): ?Guess\ValueGuess; +- public function getKey(); ++ public function getKey(): string; /** -@@ -36,5 +36,5 @@ interface FormTypeGuesserInterface - * @return Guess\ValueGuess|null + * @return void */ -- public function guessMaxLength(string $class, string $property); -+ public function guessMaxLength(string $class, string $property): ?Guess\ValueGuess; +- public function addConfiguration(NodeDefinition $builder); ++ public function addConfiguration(NodeDefinition $builder): void; + } +diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +index 37978b285f..ca1f5ae517 100644 +--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php ++++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +@@ -82,5 +82,5 @@ class SecurityExtension extends Extension implements PrependExtensionInterface + * @return void + */ +- public function prepend(ContainerBuilder $container) ++ public function prepend(ContainerBuilder $container): void + { + foreach ($this->getSortedFactories() as $factory) { +@@ -94,5 +94,5 @@ class SecurityExtension extends Extension implements PrependExtensionInterface + * @return void + */ +- public function load(array $configs, ContainerBuilder $container) ++ public function load(array $configs, ContainerBuilder $container): void + { + if (!array_filter($configs)) { +diff --git a/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php b/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php +index 0c703f79cf..7d9e956580 100644 +--- a/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php ++++ b/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php +@@ -40,5 +40,5 @@ class FirewallListener extends Firewall + * @return void + */ +- public function configureLogoutUrlGenerator(RequestEvent $event) ++ public function configureLogoutUrlGenerator(RequestEvent $event): void + { + if (!$event->isMainRequest()) { +@@ -54,5 +54,5 @@ class FirewallListener extends Firewall + * @return void + */ +- public function onKernelFinishRequest(FinishRequestEvent $event) ++ public function onKernelFinishRequest(FinishRequestEvent $event): void + { + if ($event->isMainRequest()) { +diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +index 5077c6768d..bd741840f4 100644 +--- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php ++++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +@@ -42,5 +42,5 @@ class FirewallContext + * @return FirewallConfig|null + */ +- public function getConfig() ++ public function getConfig(): ?FirewallConfig + { + return $this->config; +@@ -58,5 +58,5 @@ class FirewallContext + * @return ExceptionListener|null + */ +- public function getExceptionListener() ++ public function getExceptionListener(): ?ExceptionListener + { + return $this->exceptionListener; +@@ -66,5 +66,5 @@ class FirewallContext + * @return LogoutListener|null + */ +- public function getLogoutListener() ++ public function getLogoutListener(): ?LogoutListener + { + return $this->logoutListener; +diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +index 2cbca705f9..42cfdb0ff0 100644 +--- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php ++++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +@@ -60,5 +60,5 @@ class SecurityBundle extends Bundle + * @return void + */ +- public function build(ContainerBuilder $container) ++ public function build(ContainerBuilder $container): void + { + parent::build($container); +diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +index 5c3cff66fc..6c867b1e76 100644 +--- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php ++++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +@@ -29,5 +29,5 @@ class ExtensionPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!class_exists(Packages::class)) { +diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php +index ecb99ce20e..212ebbc96f 100644 +--- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php ++++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php +@@ -25,5 +25,5 @@ class RuntimeLoaderPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('twig.runtime_loader')) { +diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php +index 99b975edea..2c9e2d326d 100644 +--- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php ++++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php +@@ -28,5 +28,5 @@ class TwigEnvironmentPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (false === $container->hasDefinition('twig')) { +diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php +index 1da7e86797..eb6df32373 100644 +--- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php ++++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php +@@ -27,5 +27,5 @@ class TwigLoaderPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (false === $container->hasDefinition('twig')) { +diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php +index b3eec9ff60..742be71a64 100644 +--- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php ++++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php +@@ -46,5 +46,5 @@ class EnvironmentConfigurator + * @return void + */ +- public function configure(Environment $environment) ++ public function configure(Environment $environment): void + { + $environment->getExtension(CoreExtension::class)->setDateFormat($this->dateFormat, $this->intervalFormat); +diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +index 101dbf699a..81f5a7fc4c 100644 +--- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php ++++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +@@ -39,5 +39,5 @@ class TwigExtension extends Extension + * @return void + */ +- public function load(array $configs, ContainerBuilder $container) ++ public function load(array $configs, ContainerBuilder $container): void + { + $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); +diff --git a/src/Symfony/Bundle/TwigBundle/TwigBundle.php b/src/Symfony/Bundle/TwigBundle/TwigBundle.php +index 802cb536d1..d7e1017875 100644 +--- a/src/Symfony/Bundle/TwigBundle/TwigBundle.php ++++ b/src/Symfony/Bundle/TwigBundle/TwigBundle.php +@@ -31,5 +31,5 @@ class TwigBundle extends Bundle + * @return void + */ +- public function build(ContainerBuilder $container) ++ public function build(ContainerBuilder $container): void + { + parent::build($container); +@@ -45,5 +45,5 @@ class TwigBundle extends Bundle + * @return void + */ +- public function registerCommands(Application $application) ++ public function registerCommands(Application $application): void + { + // noop +diff --git a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php +index 16e6db29ee..1420b29c99 100644 +--- a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php ++++ b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php +@@ -41,5 +41,5 @@ class WebProfilerExtension extends Extension + * @return void + */ +- public function load(array $configs, ContainerBuilder $container) ++ public function load(array $configs, ContainerBuilder $container): void + { + $configuration = $this->getConfiguration($configs, $container); +diff --git a/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php b/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php +index 264b26c925..2dbc40c735 100644 +--- a/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php ++++ b/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php +@@ -22,5 +22,5 @@ class WebProfilerBundle extends Bundle + * @return void + */ +- public function boot() ++ public function boot(): void + { + if ('prod' === $this->container->getParameter('kernel.environment')) { +diff --git a/src/Symfony/Component/Asset/Packages.php b/src/Symfony/Component/Asset/Packages.php +index cffea43c49..0645fbd756 100644 +--- a/src/Symfony/Component/Asset/Packages.php ++++ b/src/Symfony/Component/Asset/Packages.php +@@ -41,5 +41,5 @@ class Packages + * @return void + */ +- public function setDefaultPackage(PackageInterface $defaultPackage) ++ public function setDefaultPackage(PackageInterface $defaultPackage): void + { + $this->defaultPackage = $defaultPackage; +@@ -49,5 +49,5 @@ class Packages + * @return void + */ +- public function addPackage(string $name, PackageInterface $package) ++ public function addPackage(string $name, PackageInterface $package): void + { + $this->packages[$name] = $package; +diff --git a/src/Symfony/Component/BrowserKit/AbstractBrowser.php b/src/Symfony/Component/BrowserKit/AbstractBrowser.php +index 5c263e6fac..3482ed2397 100644 +--- a/src/Symfony/Component/BrowserKit/AbstractBrowser.php ++++ b/src/Symfony/Component/BrowserKit/AbstractBrowser.php +@@ -67,5 +67,5 @@ abstract class AbstractBrowser + * @return void + */ +- public function followRedirects(bool $followRedirects = true) ++ public function followRedirects(bool $followRedirects = true): void + { + $this->followRedirects = $followRedirects; +@@ -77,5 +77,5 @@ abstract class AbstractBrowser + * @return void + */ +- public function followMetaRefresh(bool $followMetaRefresh = true) ++ public function followMetaRefresh(bool $followMetaRefresh = true): void + { + $this->followMetaRefresh = $followMetaRefresh; +@@ -95,5 +95,5 @@ abstract class AbstractBrowser + * @return void + */ +- public function setMaxRedirects(int $maxRedirects) ++ public function setMaxRedirects(int $maxRedirects): void + { + $this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects; +@@ -116,5 +116,5 @@ abstract class AbstractBrowser + * @throws LogicException When Symfony Process Component is not installed + */ +- public function insulate(bool $insulated = true) ++ public function insulate(bool $insulated = true): void + { + if ($insulated && !class_exists(\Symfony\Component\Process\Process::class)) { +@@ -130,5 +130,5 @@ abstract class AbstractBrowser + * @return void + */ +- public function setServerParameters(array $server) ++ public function setServerParameters(array $server): void + { + $this->server = array_merge([ +@@ -142,5 +142,5 @@ abstract class AbstractBrowser + * @return void + */ +- public function setServerParameter(string $key, string $value) ++ public function setServerParameter(string $key, string $value): void + { + $this->server[$key] = $value; +@@ -441,5 +441,5 @@ abstract class AbstractBrowser + * @throws \RuntimeException When processing returns exit code + */ +- protected function doRequestInProcess(object $request) ++ protected function doRequestInProcess(object $request): object + { + $deprecationsFile = tempnam(sys_get_temp_dir(), 'deprec'); +@@ -474,5 +474,5 @@ abstract class AbstractBrowser + * @return object + */ +- abstract protected function doRequest(object $request); ++ abstract protected function doRequest(object $request): object; /** -@@ -50,4 +50,4 @@ interface FormTypeGuesserInterface - * @return Guess\ValueGuess|null +@@ -485,5 +485,5 @@ abstract class AbstractBrowser + * @throws LogicException When this abstract class is not implemented */ -- public function guessPattern(string $class, string $property); -+ public function guessPattern(string $class, string $property): ?Guess\ValueGuess; +- protected function getScript(object $request) ++ protected function getScript(object $request): string + { + throw new LogicException('To insulate requests, you need to override the getScript() method.'); +@@ -495,5 +495,5 @@ abstract class AbstractBrowser + * @return object + */ +- protected function filterRequest(Request $request) ++ protected function filterRequest(Request $request): object + { + return $request; +@@ -505,5 +505,5 @@ abstract class AbstractBrowser + * @return Response + */ +- protected function filterResponse(object $response) ++ protected function filterResponse(object $response): Response + { + return $response; +@@ -630,5 +630,5 @@ abstract class AbstractBrowser + * @return void + */ +- public function restart() ++ public function restart(): void + { + $this->cookieJar->clear(); +diff --git a/src/Symfony/Component/BrowserKit/CookieJar.php b/src/Symfony/Component/BrowserKit/CookieJar.php +index f851f81363..311a9f4eee 100644 +--- a/src/Symfony/Component/BrowserKit/CookieJar.php ++++ b/src/Symfony/Component/BrowserKit/CookieJar.php +@@ -26,5 +26,5 @@ class CookieJar + * @return void + */ +- public function set(Cookie $cookie) ++ public function set(Cookie $cookie): void + { + $this->cookieJar[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; +@@ -73,5 +73,5 @@ class CookieJar + * @return void + */ +- public function expire(string $name, ?string $path = '/', string $domain = null) ++ public function expire(string $name, ?string $path = '/', string $domain = null): void + { + $path ??= '/'; +@@ -103,5 +103,5 @@ class CookieJar + * @return void + */ +- public function clear() ++ public function clear(): void + { + $this->cookieJar = []; +@@ -115,5 +115,5 @@ class CookieJar + * @return void + */ +- public function updateFromSetCookie(array $setCookies, string $uri = null) ++ public function updateFromSetCookie(array $setCookies, string $uri = null): void + { + $cookies = []; +@@ -143,5 +143,5 @@ class CookieJar + * @return void + */ +- public function updateFromResponse(Response $response, string $uri = null) ++ public function updateFromResponse(Response $response, string $uri = null): void + { + $this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri); +@@ -217,5 +217,5 @@ class 8000 CookieJar + * @return void + */ +- public function flushExpiredCookies() ++ public function flushExpiredCookies(): void + { + foreach ($this->cookieJar as $domain => $pathCookies) { +diff --git a/src/Symfony/Component/BrowserKit/History.php b/src/Symfony/Component/BrowserKit/History.php +index 7fce4e32b0..a7f192c5e3 100644 +--- a/src/Symfony/Component/BrowserKit/History.php ++++ b/src/Symfony/Component/BrowserKit/History.php +@@ -29,5 +29,5 @@ class History + * @return void + */ +- public function clear() ++ public function clear(): void + { + $this->stack = []; +@@ -40,5 +40,5 @@ class History + * @return void + */ +- public function add(Request $request) ++ public function add(Request $request): void + { + $this->stack = \array_slice($this->stack, 0, $this->position + 1); +diff --git a/src/Symfony/Component/BrowserKit/Tests/TestClient.php b/src/Symfony/Component/BrowserKit/Tests/TestClient.php +index c98c650298..47c76ad5e5 100644 +--- a/src/Symfony/Component/BrowserKit/Tests/TestClient.php ++++ b/src/Symfony/Component/BrowserKit/Tests/TestClient.php +@@ -42,5 +42,5 @@ class TestClient extends AbstractBrowser + } + +- protected function getScript(object $request) ++ protected function getScript(object $request): string + { + $r = new \ReflectionClass(Response::class); +diff --git a/src/Symfony/Component/BrowserKit/Tests/TestHttpClient.php b/src/Symfony/Component/BrowserKit/Tests/TestHttpClient.php +index afb0197c91..7a3c9a7ec6 100644 +--- a/src/Symfony/Component/BrowserKit/Tests/TestHttpClient.php ++++ b/src/Symfony/Component/BrowserKit/Tests/TestHttpClient.php +@@ -65,5 +65,5 @@ class TestHttpClient extends HttpBrowser + } + +- protected function getScript(object $request) ++ protected function getScript(object $request): string + { + $r = new \ReflectionClass(Response::class); +diff --git a/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php b/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php +index 3dc93fd541..8a7df7a19b 100644 +--- a/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php ++++ b/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php +@@ -51,5 +51,5 @@ class ApcuAdapter extends AbstractAdapter + * @return bool + */ +- public static function isSupported() ++ public static function isSupported(): bool + { + return \function_exists('apcu_fetch') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL); +diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +index 319dc0487b..6a4f825360 100644 +--- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php ++++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +@@ -264,5 +264,5 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter + * @return void + */ +- public function reset() ++ public function reset(): void + { + $this->clear(); +diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +index ffaa56f3ed..c63206e18d 100644 +--- a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php ++++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +@@ -284,5 +284,5 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa + * @return void + */ +- public function reset() ++ public function reset(): void + { + foreach ($this->adapters as $adapter) { +diff --git a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php b/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php +index 054f6d1957..68731fb028 100644 +--- a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php ++++ b/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php +@@ -71,5 +71,5 @@ class MemcachedAdapter extends AbstractAdapter + * @return bool + */ +- public static function isSupported() ++ public static function isSupported(): bool + { + return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '3.1.6', '>='); +diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +index dfffb3bd13..e4384af6cc 100644 +--- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php ++++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +@@ -101,5 +101,5 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface + * @throws \DomainException When an unsupported PDO driver is used + */ +- public function createTable() ++ public function createTable(): void + { + // connect if we are not yet +diff --git a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php +index 6e4e1dffa3..bab8a91ca4 100644 +--- a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php ++++ b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php +@@ -58,5 +58,5 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface + * @return bool + */ +- public static function isSupported() ++ public static function isSupported(): bool + { + self::$startTime ??= $_SERVER['REQUEST_TIME'] ?? time(); +@@ -281,5 +281,5 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface + * @return bool + */ +- protected function doUnlink(string $file) ++ protected function doUnlink(string $file): bool + { + unset(self::$valuesCache[$file]); +diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +index f64ac99c11..84d32fb424 100644 +--- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php ++++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +@@ -283,5 +283,5 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac + * @return void + */ +- public function reset() ++ public function reset(): void + { + $this->commit(); +diff --git a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php +index 118b009099..ba388b3ad8 100644 +--- a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php ++++ b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php +@@ -196,5 +196,5 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt + * @return void + */ +- public function reset() ++ public function reset(): void + { + if ($this->pool instanceof ResetInterface) { +@@ -218,5 +218,5 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt + * @return array + */ +- public function getCalls() ++ public function getCalls(): array + { + return $this->calls; +@@ -226,5 +226,5 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt + * @return void + */ +- public function clearCalls() ++ public function clearCalls(): void + { + $this->calls = []; +@@ -239,5 +239,5 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt + * @return TraceableAdapterEvent + */ +- protected function start(string $name) ++ protected function start(string $name): TraceableAdapterEvent + { + $this->calls[] = $event = new TraceableAdapterEvent(); +diff --git a/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php b/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php +index b50ca12308..6c39f61e89 100644 +--- a/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php ++++ b/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php +@@ -30,5 +30,5 @@ class CacheCollectorPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('data_collector.cache')) { +diff --git a/src/Symfony/Component/Cache/DependencyInjection/CachePoolClearerPass.php b/src/Symfony/Component/Cache/DependencyInjection/CachePoolClearerPass.php +index 6793bea94c..230575ef42 100644 +--- a/src/Symfony/Component/Cache/DependencyInjection/CachePoolClearerPass.php ++++ b/src/Symfony/Component/Cache/DependencyInjection/CachePoolClearerPass.php +@@ -24,5 +24,5 @@ class CachePoolClearerPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $container->getParameterBag()->remove('cache.prefix.seed'); +diff --git a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php +index 5055ba9918..3d92c9844f 100644 +--- a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php ++++ b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php +@@ -33,5 +33,5 @@ class CachePoolPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if ($container->hasParameter('cache.prefix.seed')) { +diff --git a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPrunerPass.php b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPrunerPass.php +index 00e912686b..58872ec2bc 100644 +--- a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPrunerPass.php ++++ b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPrunerPass.php +@@ -27,5 +27,5 @@ class CachePoolPrunerPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('console.command.cache_pool_prune')) { +diff --git a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php +index 200406a564..0bad73fe6a 100644 +--- a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php ++++ b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php +@@ -81,5 +81,5 @@ trait FilesystemCommonTrait + * @return bool + */ +- protected function doUnlink(string $file) ++ protected function doUnlink(string $file): bool + { + return @unlink($file); +diff --git a/src/Symfony/Component/Config/ConfigCacheInterface.php b/src/Symfony/Component/Config/ConfigCacheInterface.php +index be7f0986c3..fa5347e34b 100644 +--- a/src/Symfony/Component/Config/ConfigCacheInterface.php ++++ b/src/Symfony/Component/Config/ConfigCacheInterface.php +@@ -44,4 +44,4 @@ interface ConfigCacheInterface + * @throws \RuntimeException When the cache file cannot be written + */ +- public function write(string $content, array $metadata = null); ++ public function write(string $content, array $metadata = null): void; } -diff --git a/src/Symfony/Component/Form/FormTypeInterface.php b/src/Symfony/Component/Form/FormTypeInterface.php -index 2b9066a511..1c9e9f5a26 100644 ---- a/src/Symfony/Component/Form/FormTypeInterface.php -+++ b/src/Symfony/Component/Form/FormTypeInterface.php -@@ -77,5 +77,5 @@ interface FormTypeInterface - * @return string +diff --git a/src/Symfony/Component/Config/Definition/ArrayNode.php b/src/Symfony/Component/Config/Definition/ArrayNode.php +index 1448220cdd..12935fb079 100644 +--- a/src/Symfony/Component/Config/Definition/ArrayNode.php ++++ b/src/Symfony/Component/Config/Definition/ArrayNode.php +@@ -36,5 +36,5 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface + * @return void */ -- public function getBlockPrefix(); -+ public function getBlockPrefix(): string; +- public function setNormalizeKeys(bool $normalizeKeys) ++ public function setNormalizeKeys(bool $normalizeKeys): void + { + $this->normalizeKeys = $normalizeKeys; +@@ -84,5 +84,5 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- public function setXmlRemappings(array $remappings) ++ public function setXmlRemappings(array $remappings): void + { + $this->xmlRemappings = $remappings; +@@ -105,5 +105,5 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- public function setAddIfNotSet(bool $boolean) ++ public function setAddIfNotSet(bool $boolean): void + { + $this->addIfNotSet = $boolean; +@@ -115,5 +115,5 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- public function setAllowFalse(bool $allow) ++ public function setAllowFalse(bool $allow): void + { + $this->allowFalse = $allow; +@@ -125,5 +125,5 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- public function setAllowNewKeys(bool $allow) ++ public function setAllowNewKeys(bool $allow): void + { + $this->allowNewKeys = $allow; +@@ -135,5 +135,5 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- public function setPerformDeepMerging(bool $boolean) ++ public function setPerformDeepMerging(bool $boolean): void + { + $this->performDeepMerging = $boolean; +@@ -148,5 +148,5 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- public function setIgnoreExtraKeys(bool $boolean, bool $remove = true) ++ public function setIgnoreExtraKeys(bool $boolean, bool $remove = true): void + { + $this->ignoreExtraKeys = $boolean; +@@ -165,5 +165,5 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + $this->name = $name; +@@ -199,5 +199,5 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface + * @throws \InvalidArgumentException when the child node's name is not unique + */ +- public function addChild(NodeInterface $node) ++ public function addChild(NodeInterface $node): void + { + $name = $node->getName(); +@@ -262,5 +262,5 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- protected function validateType(mixed $value) ++ protected function validateType(mixed $value): void + { + if (!\is_array($value) && (!$this->allowFalse || false !== $value)) { +diff --git a/src/Symfony/Component/Config/Definition/BaseNode.php b/src/Symfony/Component/Config/Definition/BaseNode.php +index 85f0f7eebd..f2c3ca0d0d 100644 +--- a/src/Symfony/Component/Config/Definition/BaseNode.php ++++ b/src/Symfony/Component/Config/Definition/BaseNode.php +@@ -102,5 +102,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function setAttribute(string $key, mixed $value) ++ public function setAttribute(string $key, mixed $value): void + { + $this->attributes[$key] = $value; +@@ -125,5 +125,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function setAttributes(array $attributes) ++ public function setAttributes(array $attributes): void + { + $this->attributes = $attributes; +@@ -133,5 +133,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function removeAttribute(string $key) ++ public function removeAttribute(string $key): void + { + unset($this->attributes[$key]); +@@ -143,5 +143,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function setInfo(string $info) ++ public function setInfo(string $info): void + { + $this->setAttribute('info', $info); +@@ -161,5 +161,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function setExample(string|array $example) ++ public function setExample(string|array $example): void + { + $this->setAttribute('example', $example); +@@ -179,5 +179,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function addEquivalentValue(mixed $originalValue, mixed $equivalentValue) ++ public function addEquivalentValue(mixed $originalValue, mixed $equivalentValue): void + { + $this->equivalentValues[] = [$originalValue, $equivalentValue]; +@@ -189,5 +189,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function setRequired(bool $boolean) ++ public function setRequired(bool $boolean): void + { + $this->required = $boolean; +@@ -206,5 +206,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function setDeprecated(string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.') ++ public function setDeprecated(string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.'): void + { + $this->deprecation = [ +@@ -220,5 +220,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function setAllowOverwrite(bool $allow) ++ public function setAllowOverwrite(bool $allow): void + { + $this->allowOverwrite = $allow; +@@ -232,5 +232,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function setNormalizationClosures(array $closures) ++ public function setNormalizationClosures(array $closures): void + { + $this->normalizationClosures = $closures; +@@ -244,5 +244,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function setNormalizedTypes(array $types) ++ public function setNormalizedTypes(array $types): void + { + $this->normalizedTypes = $types; +@@ -266,5 +266,5 @@ abstract class BaseNode implements NodeInterface + * @return void + */ +- public function setFinalValidationClosures(array $closures) ++ public function setFinalValidationClosures(array $closures): void + { + $this->finalValidationClosures = $closures; +@@ -447,5 +447,5 @@ abstract class BaseNode implements NodeInterface + * @throws InvalidTypeException when the value is invalid + */ +- abstract protected function validateType(mixed $value); ++ abstract protected function validateType(mixed $value): void; /** -@@ -84,4 +84,4 @@ interface FormTypeInterface - * @return string|null +diff --git a/src/Symfony/Component/Config/Definition/BooleanNode.php b/src/Symfony/Component/Config/Definition/BooleanNode.php +index 7ec903cd67..9a86de2559 100644 +--- a/src/Symfony/Component/Config/Definition/BooleanNode.php ++++ b/src/Symfony/Component/Config/Definition/BooleanNode.php +@@ -24,5 +24,5 @@ class BooleanNode extends ScalarNode + * @return void */ -- public function getParent(); -+ public function getParent(): ?string; +- protected function validateType(mixed $value) ++ protected function validateType(mixed $value): void + { + if (!\is_bool($value)) { +diff --git a/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php +index 0110f0502f..a5754c1464 100644 +--- a/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php ++++ b/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php +@@ -49,5 +49,5 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition + * @return void + */ +- public function setBuilder(NodeBuilder $builder) ++ public function setBuilder(NodeBuilder $builder): void + { + $this->nodeBuilder = $builder; +@@ -430,5 +430,5 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition + * @throws InvalidDefinitionException + */ +- protected function validateConcreteNode(ArrayNode $node) ++ protected function validateConcreteNode(ArrayNode $node): void + { + $path = $node->getPath(); +@@ -462,5 +462,5 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition + * @throws InvalidDefinitionException + */ +- protected function validatePrototypeNode(PrototypedArrayNode $node) ++ protected function validatePrototypeNode(PrototypedArrayNode $node): void + { + $path = $node->getPath(); +diff --git a/src/Symfony/Component/Config/Definition/Builder/BuilderAwareInterface.php b/src/Symfony/Component/Config/Definition/Builder/BuilderAwareInterface.php +index bb40307e17..998fb85b27 100644 +--- a/src/Symfony/Component/Config/Definition/Builder/BuilderAwareInterface.php ++++ b/src/Symfony/Component/Config/Definition/Builder/BuilderAwareInterface.php +@@ -24,4 +24,4 @@ interface BuilderAwareInterface + * @return void + */ +- public function setBuilder(NodeBuilder $builder); ++ public function setBuilder(NodeBuilder $builder): void; } -diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php -index 1f1740b7e2..22dc8ea26a 100644 ---- a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php -+++ b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php -@@ -29,4 +29,4 @@ interface CacheWarmerInterface extends WarmableInterface - * @return bool +diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php +index 7cda0bc7d8..b2311826f4 100644 +--- a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php ++++ b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php +@@ -111,5 +111,5 @@ class NodeBuilder implements NodeParentInterface + * @return NodeDefinition&ParentNodeDefinitionInterface */ -- public function isOptional(); -+ public function isOptional(): bool; +- public function end() ++ public function end(): NodeDefinition&ParentNodeDefinitionInterface + { + return $this->parent; +diff --git a/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php +index 4f868f7031..190b720b61 100644 +--- a/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php ++++ b/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php +@@ -55,5 +55,5 @@ class TreeBuilder implements NodeParentInterface + * @return void + */ +- public function setPathSeparator(string $separator) ++ public function setPathSeparator(string $separator): void + { + // unset last built as changing path separator changes all nodes +diff --git a/src/Symfony/Component/Config/Definition/ConfigurationInterface.php b/src/Symfony/Component/Config/Definition/ConfigurationInterface.php +index 7b5d443fe6..d64ae0d024 100644 +--- a/src/Symfony/Component/Config/Definition/ConfigurationInterface.php ++++ b/src/Symfony/Component/Config/Definition/ConfigurationInterface.php +@@ -26,4 +26,4 @@ interface ConfigurationInterface + * @return TreeBuilder + */ +- public function getConfigTreeBuilder(); ++ public function getConfigTreeBuilder(): TreeBuilder; } -diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php b/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php -index 2f442cb536..d98909cfae 100644 ---- a/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php -+++ b/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php -@@ -24,4 +24,4 @@ interface WarmableInterface - * @return string[] A list of classes or files to preload on PHP 7.4+ +diff --git a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php +index 34f93ce07d..0e7f1317d3 100644 +--- a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php ++++ b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php +@@ -35,5 +35,5 @@ class XmlReferenceDumper + * @return string + */ +- public function dump(ConfigurationInterface $configuration, string $namespace = null) ++ public function dump(ConfigurationInterface $configuration, string $namespace = null): string + { + return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace); +@@ -43,5 +43,5 @@ class XmlReferenceDumper + * @return string + */ +- public function dumpNode(NodeInterface $node, string $namespace = null) ++ public function dumpNode(NodeInterface $node, string $namespace = null): string + { + $this->reference = ''; +diff --git a/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php b/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php +index 97a391adab..1a59318a40 100644 +--- a/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php ++++ b/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php +@@ -34,5 +34,5 @@ class YamlReferenceDumper + * @return string + */ +- public function dump(ConfigurationInterface $configuration) ++ public function dump(ConfigurationInterface $configuration): string + { + return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree()); +@@ -42,5 +42,5 @@ class YamlReferenceDumper + * @return string + */ +- public function dumpAtPath(ConfigurationInterface $configuration, string $path) ++ public function dumpAtPath(ConfigurationInterface $configuration, string $path): string + { + $rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree(); +@@ -71,5 +71,5 @@ class YamlReferenceDumper + * @return string + */ +- public function dumpNode(NodeInterface $node) ++ public function dumpNode(NodeInterface $node): string + { + $this->reference = ''; +diff --git a/src/Symfony/Component/Config/Definition/EnumNode.php b/src/Symfony/Component/Config/Definition/EnumNode.php +index 4edeae9040..d562308fa2 100644 +--- a/src/Symfony/Component/Config/Definition/EnumNode.php ++++ b/src/Symfony/Component/Config/Definition/EnumNode.php +@@ -50,5 +50,5 @@ class EnumNode extends ScalarNode + * @return array + */ +- public function getValues() ++ public function getValues(): array + { + return $this->values; +@@ -72,5 +72,5 @@ class EnumNode extends ScalarNode + * @return void + */ +- protected function validateType(mixed $value) ++ protected function validateType(mixed $value): void + { + if ($value instanceof \UnitEnum) { +diff --git a/src/Symfony/Component/Config/Definition/Exception/InvalidConfigurationException.php b/src/Symfony/Component/Config/Definition/Exception/InvalidConfigurationException.php +index 794447bf8d..42db1e5a40 100644 +--- a/src/Symfony/Component/Config/Definition/Exception/InvalidConfigurationException.php ++++ b/src/Symfony/Component/Config/Definition/Exception/InvalidConfigurationException.php +@@ -26,5 +26,5 @@ class InvalidConfigurationException extends Exception + * @return void + */ +- public function setPath(string $path) ++ public function setPath(string $path): void + { + $this->path = $path; +@@ -41,5 +41,5 @@ class InvalidConfigurationException extends Exception + * @return void + */ +- public function addHint(string $hint) ++ public function addHint(string $hint): void + { + if (!$this->containsHints) { +diff --git a/src/Symfony/Component/Config/Definition/FloatNode.php b/src/Symfony/Component/Config/Definition/FloatNode.php +index ce4193e09c..64344cadb6 100644 +--- a/src/Symfony/Component/Config/Definition/FloatNode.php ++++ b/src/Symfony/Component/Config/Definition/FloatNode.php +@@ -24,5 +24,5 @@ class FloatNode extends NumericNode + * @return void + */ +- protected function validateType(mixed $value) ++ protected function validateType(mixed $value): void + { + // Integers are also accepted, we just cast them +diff --git a/src/Symfony/Component/Config/Definition/IntegerNode.php b/src/Symfony/Component/Config/Definition/IntegerNode.php +index 4a3e3253ce..09957cd846 100644 +--- a/src/Symfony/Component/Config/Definition/IntegerNode.php ++++ b/src/Symfony/Component/Config/Definition/IntegerNode.php +@@ -24,5 +24,5 @@ class IntegerNode extends NumericNode + * @return void + */ +- protected function validateType(mixed $value) ++ protected function validateType(mixed $value): void + { + if (!\is_int($value)) { +diff --git a/src/Symfony/Component/Config/Definition/PrototypeNodeInterface.php b/src/Symfony/Component/Config/Definition/PrototypeNodeInterface.php +index 9dce7444b0..46ab38e3ff 100644 +--- a/src/Symfony/Component/Config/Definition/PrototypeNodeInterface.php ++++ b/src/Symfony/Component/Config/Definition/PrototypeNodeInterface.php +@@ -24,4 +24,4 @@ interface PrototypeNodeInterface extends NodeInterface + * @return void + */ +- public function setName(string $name); ++ public function setName(string $name): void; + } +diff --git a/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php b/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php +index c105ac1f30..1ea0880c9b 100644 +--- a/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php ++++ b/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php +@@ -41,5 +41,5 @@ class PrototypedArrayNode extends ArrayNode + * @return void + */ +- public function setMinNumberOfElements(int $number) ++ public function setMinNumberOfElements(int $number): void + { + $this->minNumberOfElements = $number; +@@ -72,5 +72,5 @@ class PrototypedArrayNode extends ArrayNode + * @return void + */ +- public function setKeyAttribute(string $attribute, bool $remove = true) ++ public function setKeyAttribute(string $attribute, bool $remove = true): void + { + $this->keyAttribute = $attribute; +@@ -91,5 +91,5 @@ class PrototypedArrayNode extends ArrayNode + * @return void + */ +- public function setDefaultValue(array $value) ++ public function setDefaultValue(array $value): void + { + $this->defaultValue = $value; +@@ -108,5 +108,5 @@ class PrototypedArrayNode extends ArrayNode + * @return void + */ +- public function setAddChildrenIfNoneSet(int|string|array|null $children = ['defaults']) ++ public function setAddChildrenIfNoneSet(int|string|array|null $children = ['defaults']): void + { + if (null === $children) { +@@ -141,5 +141,5 @@ class PrototypedArrayNode extends ArrayNode + * @return void + */ +- public function setPrototype(PrototypeNodeInterface $node) ++ public function setPrototype(PrototypeNodeInterface $node): void + { + $this->prototype = $node; +@@ -161,5 +161,5 @@ class PrototypedArrayNode extends ArrayNode + * @throws Exception + */ +- public function addChild(NodeInterface $node) ++ public function addChild(NodeInterface $node): never + { + throw new Exception('A prototyped array node cannot have concrete children.'); +diff --git a/src/Symfony/Component/Config/Definition/ScalarNode.php b/src/Symfony/Component/Config/Definition/ScalarNode.php +index e11fa1ee1c..ee27f874d0 100644 +--- a/src/Symfony/Component/Config/Definition/ScalarNode.php ++++ b/src/Symfony/Component/Config/Definition/ScalarNode.php +@@ -31,5 +31,5 @@ class ScalarNode extends VariableNode + * @return void + */ +- protected function validateType(mixed $value) ++ protected function validateType(mixed $value): void + { + if (!\is_scalar($value) && null !== $value) { +diff --git a/src/Symfony/Component/Config/Definition/VariableNode.php b/src/Symfony/Component/Config/Definition/VariableNode.php +index 6bdc65b4e7..c5a3fb4f6f 100644 +--- a/src/Symfony/Component/Config/Definition/VariableNode.php ++++ b/src/Symfony/Component/Config/Definition/ 8000 VariableNode.php +@@ -31,5 +31,5 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- public function setDefaultValue(mixed $value) ++ public function setDefaultValue(mixed $value): void + { + $this->defaultValueSet = true; +@@ -56,5 +56,5 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- public function setAllowEmptyValue(bool $boolean) ++ public function setAllowEmptyValue(bool $boolean): void + { + $this->allowEmptyValue = $boolean; +@@ -64,5 +64,5 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + $this->name = $name; +@@ -72,5 +72,5 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface + * @return void + */ +- protected function validateType(mixed $value) ++ protected function validateType(mixed $value): void + { + } +diff --git a/src/Symfony/Component/Config/Exception/FileLocatorFileNotFoundException.php b/src/Symfony/Component/Config/Exception/FileLocatorFileNotFoundException.php +index c5173ae580..1f5aa3616e 100644 +--- a/src/Symfony/Component/Config/Exception/FileLocatorFileNotFoundException.php ++++ b/src/Symfony/Component/Config/Exception/FileLocatorFileNotFoundException.php +@@ -31,5 +31,5 @@ class FileLocatorFileNotFoundException extends \InvalidArgumentException + * @return array + */ +- public function getPaths() ++ public function getPaths(): array + { + return $this->paths; +diff --git a/src/Symfony/Component/Config/Exception/LoaderLoadException.php b/src/Symfony/Component/Config/Exception/LoaderLoadException.php +index 57afd6a8db..3d662340ea 100644 +--- a/src/Symfony/Component/Config/Exception/LoaderLoadException.php ++++ b/src/Symfony/Component/Config/Exception/LoaderLoadException.php +@@ -80,5 +80,5 @@ class LoaderLoadException extends \Exception + * @return string + */ +- protected function varToString(mixed $var) ++ protected function varToString(mixed $var): string + { + if (\is_object($var)) { +diff --git a/src/Symfony/Component/Config/FileLocator.php b/src/Symfony/Component/Config/FileLocator.php +index e147d9b1aa..6b0ce493ea 100644 +--- a/src/Symfony/Component/Config/FileLocator.php ++++ b/src/Symfony/Component/Config/FileLocator.php +@@ -34,5 +34,5 @@ class FileLocator implements FileLocatorInterface + * @return string|array + */ +- public function locate(string $name, string $currentPath = null, bool $first = true) ++ public function locate(string $name, string $currentPath = null, bool $first = true): string|array + { + if ('' === $name) { +diff --git a/src/Symfony/Component/Config/FileLocatorInterface.php b/src/Symfony/Component/Config/FileLocatorInterface.php +index e3ca1d49c4..526d350484 100644 +--- a/src/Symfony/Component/Config/FileLocatorInterface.php ++++ b/src/Symfony/Component/Config/FileLocatorInterface.php +@@ -31,4 +31,4 @@ interface FileLocatorInterface + * @throws FileLocatorFileNotFoundException If a file is not found + */ +- public function locate(string $name, string $currentPath = null, bool $first = true); ++ public function locate(string $name, string $currentPath = null, bool $first = true): string|array; + } +diff --git a/src/Symfony/Component/Config/Loader/FileLoader.php b/src/Symfony/Component/Config/Loader/FileLoader.php +index 8cfaa23ba2..68a4120506 100644 +--- a/src/Symfony/Component/Config/Loader/FileLoader.php ++++ b/src/Symfony/Component/Config/Loader/FileLoader.php +@@ -43,5 +43,5 @@ abstract class FileLoader extends Loader + * @return void + */ +- public function setCurrentDir(string $dir) ++ public function setCurrentDir(string $dir): void + { + $this->currentDir = $dir; +@@ -71,5 +71,5 @@ abstract class FileLoader extends Loader + * @throws FileLocatorFileNotFoundException + */ +- public function import(mixed $resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null, string|array $exclude = null) ++ public function import(mixed $resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null, string|array $exclude = null): mixed + { + if (\is_string($resource) && \strlen($resource) !== ($i = strcspn($resource, '*?{[')) && !str_contains($resource, "\n")) { +diff --git a/src/Symfony/Component/Config/Loader/Loader.php b/src/Symfony/Component/Config/Loader/Loader.php +index 36e85ad346..bb6d9ca2fe 100644 +--- a/src/Symfony/Component/Config/Loader/Loader.php ++++ b/src/Symfony/Component/Config/Loader/Loader.php +@@ -37,5 +37,5 @@ abstract class Loader implements LoaderInterface + * @return void + */ +- public function setResolver(LoaderResolverInterface $resolver) ++ public function setResolver(LoaderResolverInterface $resolver): void + { + $this->resolver = $resolver; +@@ -47,5 +47,5 @@ abstract class Loader implements LoaderInterface + * @return mixed + */ +- public function import(mixed $resource, string $type = null) ++ public function import(mixed $resource, string $type = null): mixed + { + return $this->resolve($resource, $type)->load($resource, $type); +diff --git a/src/Symfony/Component/Config/Loader/LoaderInterface.php b/src/Symfony/Component/Config/Loader/LoaderInterface.php +index 4e0746d4d6..c080bd63a9 100644 +--- a/src/Symfony/Component/Config/Loader/LoaderInterface.php ++++ b/src/Symfony/Component/Config/Loader/LoaderInterface.php +@@ -26,5 +26,5 @@ interface LoaderInterface + * @throws \Exception If something went wrong + */ +- public function load(mixed $resource, string $type = null); ++ public function load(mixed $resource, string $type = null): mixed; + + /** +@@ -35,5 +35,5 @@ interface LoaderInterface + * @return bool + */ +- public function supports(mixed $resource, string $type = null); ++ public function supports(mixed $resource, string $type = null): bool; + + /** +@@ -42,5 +42,5 @@ interface LoaderInterface + * @return LoaderResolverInterface + */ +- public function getResolver(); ++ public function getResolver(): LoaderResolverInterface; + + /** +@@ -49,4 +49,4 @@ interface LoaderInterface + * @return void + */ +- public function setResolver(LoaderResolverInterface $resolver); ++ public function setResolver(LoaderResolverInterface $resolver): void; + } +diff --git a/src/Symfony/Component/Config/Loader/LoaderResolver.php b/src/Symfony/Component/Config/Loader/LoaderResolver.php +index 670e320122..134e4069e7 100644 +--- a/src/Symfony/Component/Config/Loader/LoaderResolver.php ++++ b/src/Symfony/Component/Config/Loader/LoaderResolver.php +@@ -51,5 +51,5 @@ class LoaderResolver implements LoaderResolverInterface + * @return void + */ +- public function addLoader(LoaderInterface $loader) ++ public function addLoader(LoaderInterface $loader): void + { + $this->loaders[] = $loader; +diff --git a/src/Symfony/Component/Config/ResourceCheckerConfigCache.php b/src/Symfony/Component/Config/ResourceCheckerConfigCache.php +index a8478a8cc3..d2ec80ec99 100644 +--- a/src/Symfony/Component/Config/ResourceCheckerConfigCache.php ++++ b/src/Symfony/Component/Config/ResourceCheckerConfigCache.php +@@ -110,5 +110,5 @@ class ResourceCheckerConfigCache implements ConfigCacheInterface + * @throws \RuntimeException When cache file can't be written + */ +- public function write(string $content, array $metadata = null) ++ public function write(string $content, array $metadata = null): void + { + $mode = 0666; +diff --git a/src/Symfony/Component/Config/ResourceCheckerInterface.php b/src/Symfony/Component/Config/ResourceCheckerInterface.php +index 6b1c6c5fbe..bb80ed461e 100644 +--- a/src/Symfony/Component/Config/ResourceCheckerInterface.php ++++ b/src/Symfony/Component/Config/ResourceCheckerInterface.php +@@ -33,5 +33,5 @@ interface ResourceCheckerInterface + * @return bool + */ +- public function supports(ResourceInterface $metadata); ++ public function supports(ResourceInterface $metadata): bool; + + /** +@@ -42,4 +42,4 @@ interface ResourceCheckerInterface + * @return bool + */ +- public function isFresh(ResourceInterface $resource, int $timestamp); ++ public function isFresh(ResourceInterface $resource, int $timestamp): bool; + } +diff --git a/src/Symfony/Component/Config/Util/XmlUtils.php b/src/Symfony/Component/Config/Util/XmlUtils.php +index cc024da461..00b79e915f 100644 +--- a/src/Symfony/Component/Config/Util/XmlUtils.php ++++ b/src/Symfony/Component/Config/Util/XmlUtils.php +@@ -242,5 +242,5 @@ class XmlUtils + * @return array + */ +- protected static function getXmlErrors(bool $internalErrors) ++ protected static function getXmlErrors(bool $internalErrors): array + { + $errors = []; +diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php +index b7aaa6a29e..dd14cf2c5c 100644 +--- a/src/Symfony/Component/Console/Application.php ++++ b/src/Symfony/Component/Console/Application.php +@@ -114,5 +114,5 @@ class Application implements ResetInterface + * @return void + */ +- public function setCommandLoader(CommandLoaderInterface $commandLoader) ++ public function setCommandLoader(CommandLoaderInterface $commandLoader): void + { + $this->commandLoader = $commandLoader; +@@ -131,5 +131,5 @@ class Application implements ResetInterface + * @return void + */ +- public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent) ++ public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent): void + { + $this->signalsToDispatchEvent = $signalsToDispatchEvent; +@@ -221,5 +221,5 @@ class Application implements ResetInterface + * @return int 0 if everything went fine, or an error code + */ +- public function doRun(InputInterface $input, OutputInterface $output) ++ public function doRun(InputInterface $input, OutputInterface $output): int + { + if (true === $input->hasParameterOption(['--version', '-V'], true)) { +@@ -327,5 +327,5 @@ class Application implements ResetInterface + * @return void + */ +- public function reset() ++ public function reset(): void + { + } +@@ -334,5 +334,5 @@ class Application implements ResetInterface + * @return void + */ +- public function setHelperSet(HelperSet $helperSet) ++ public function setHelperSet(HelperSet $helperSet): void + { + $this->helperSet = $helperSet; +@@ -350,5 +350,5 @@ class Application implements ResetInterface + * @return void + */ +- public function setDefinition(InputDefinition $definition) ++ public function setDefinition(InputDefinition $definition): void + { + $this->definition = $definition; +@@ -423,5 +423,5 @@ class Application implements ResetInterface + * @return void + */ +- public function setCatchExceptions(bool $boolean) ++ public function setCatchExceptions(bool $boolean): void + { + $this->catchExceptions = $boolean; +@@ -441,5 +441,5 @@ class Application implements ResetInterface + * @return void + */ +- public function setAutoExit(bool $boolean) ++ public function setAutoExit(bool $boolean): void + { + $this->autoExit = $boolean; +@@ -459,5 +459,5 @@ class Application implements ResetInterface + * @return void + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + $this->name = $name; +@@ -477,5 +477,5 @@ class Application implements ResetInterface + * @return void + */ +- public function setVersion(string $version) ++ public function setVersion(string $version): void + { + $this->version = $version; +@@ -487,5 +487,5 @@ class Application implements ResetInterface + * @return string + */ +- public function getLongVersion() ++ public function getLongVersion(): string + { + if ('UNKNOWN' !== $this->getName()) { +@@ -517,5 +517,5 @@ class Application implements ResetInterface + * @return void + */ +- public function addCommands(array $commands) ++ public function addCommands(array $commands): void + { + foreach ($commands as $command) { +@@ -532,5 +532,5 @@ class Application implements ResetInterface + * @return Command|null + */ +- public function add(Command $command) ++ public function add(Command $command): ?Command + { + $this->init(); +@@ -569,5 +569,5 @@ class Application implements ResetInterface + * @throws CommandNotFoundException When given command name does not exist + */ +- public function get(string $name) ++ public function get(string $name): Command + { + $this->init(); +@@ -676,5 +676,5 @@ class Application implements ResetInterface + * @throws CommandNotFoundException When command name is incorrect or ambiguous + */ +- public function find(string $name) ++ public function find(string $name): Command + { + $this->init(); +@@ -784,5 +784,5 @@ class Application implements ResetInterface + * @return Command[] + */ +- public function all(string $namespace = null) ++ public function all(string $namespace = null): array + { + $this->init(); +@@ -928,5 +928,5 @@ class Application implements ResetInterface + * @return void + */ +- protected function configureIO(InputInterface $input, OutputInterface $output) ++ protected function configureIO(InputInterface $input, OutputInterface $output): void + { + if (true === $input->hasParameterOption(['--ansi'], true)) { +@@ -993,5 +993,5 @@ class Application implements ResetInterface + * @return int 0 if everything went fine, or an error code + */ +- protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) ++ protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int + { + foreach ($command->getHelperSet() as $helper) { +diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php +index 704b112d1a..b296bffcbd 100644 +--- a/src/Symfony/Component/Console/Command/Command.php ++++ b/src/Symfony/Component/Console/Command/Command.php +@@ -145,5 +145,5 @@ class Command + * @return void + */ +- public function ignoreValidationErrors() ++ public function ignoreValidationErrors(): void + { + $this->ignoreValidationErrors = true; +@@ -153,5 +153,5 @@ class Command + * @return void + */ +- public function setApplication(Application $application = null) ++ public function setApplication(Application $application = null): void + { + if (1 > \func_num_args()) { +@@ -171,5 +171,5 @@ class Command + * @return void + */ +- public function setHelperSet(HelperSet $helperSet) ++ public function setHelperSet(HelperSet $helperSet): void + { + $this->helperSet = $helperSet; +@@ -200,5 +200,5 @@ class Command + * @return bool + */ +- public function isEnabled() ++ public function isEnabled(): bool + { + return true; +@@ -210,5 +210,5 @@ class Command + * @return void + */ +- protected function configure() ++ protected function configure(): void + { + } +@@ -228,5 +228,5 @@ class Command + * @see setCode() + */ +- protected function execute(InputInterface $input, OutputInterface $output) ++ protected function execute(InputInterface $input, OutputInterface $output): int + { + throw new LogicException('You must override the execute() method in the concrete command class.'); +@@ -242,5 +242,5 @@ class Command + * @return void + */ +- protected function interact(InputInterface $input, OutputInterface $output) ++ protected function interact(InputInterface $input, OutputInterface $output): void + { + } +@@ -258,5 +258,5 @@ class Command + * @return void + */ +- protected function initialize(InputInterface $input, OutputInterface $output) ++ protected function initialize(InputInterface $input, OutputInterface $output): void + { + } +@@ -701,5 +701,5 @@ class Command + * @throws InvalidArgumentException if the helper is not defined + */ +- public function getHelper(string $name): mixed ++ public function getHelper(string $name): HelperInterface + { + if (null === $this->helperSet) { +diff --git a/src/Symfony/Component/Console/Command/HelpCommand.php b/src/Symfony/Component/Console/Command/HelpCommand.php +index e6447b0506..be85331bc3 100644 +--- a/src/Symfony/Component/Console/Command/HelpCommand.php ++++ b/src/Symfony/Component/Console/Command/HelpCommand.php +@@ -31,5 +31,5 @@ class HelpCommand extends Command + * @return void + */ +- protected function configure() ++ protected function configure(): void + { + $this->ignoreValidationErrors(); +@@ -61,5 +61,5 @@ EOF + * @return void + */ +- public function setCommand(Command $command) ++ public function setCommand(Command $command): void + { + $this->command = $command; +diff --git a/src/Symfony/Component/Console/Command/ListCommand.php b/src/Symfony/Component/Console/Command/ListCommand.php +index 5850c3d7b8..e2371f88fd 100644 +--- a/src/Symfony/Component/Console/Command/ListCommand.php ++++ b/src/Symfony/Component/Console/Command/ListCommand.php +@@ -29,5 +29,5 @@ class ListCommand extends Command + * @return void + */ +- protected function configure() ++ protected function configure(): void + { + $this +diff --git a/src/Symfony/Component/Console/Command/SignalableCommandInterface.php b/src/Symfony/Component/Console/Command/SignalableCommandInterface.php +index 4d0876003d..d33732acb6 100644 +--- a/src/Symfony/Component/Console/Command/SignalableCommandInterface.php ++++ b/src/Symfony/Component/Console/Command/SignalableCommandInterface.php +@@ -31,4 +31,4 @@ interface SignalableCommandInterface + * @return int|false The exit code to return or false to continue the normal execution + */ +- public function handleSignal(int $signal, /* int|false $previousExitCode = 0 */); ++ public function handleSignal(int $signal, /* int|false $previousExitCode = 0 */): int|false; + } +diff --git a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php +index 27705ddb63..1b25473f75 100644 +--- a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php ++++ b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php +@@ -33,5 +33,5 @@ class AddConsoleCommandPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $commandServices = $container->findTaggedServiceIds('console.command', true); +diff --git a/src/Symfony/Component/Console/Descriptor/DescriptorInterface.php b/src/Symfony/Component/Console/Descriptor/DescriptorInterface.php +index ab468a2562..e20f80d56b 100644 +--- a/src/Symfony/Component/Console/Descriptor/DescriptorInterface.php ++++ b/src/Symfony/Component/Console/Descriptor/DescriptorInterface.php +@@ -24,4 +24,4 @@ interface DescriptorInterface + * @return void + */ +- public function describe(OutputInterface $output, object $object, array $options = []); ++ public function describe(OutputInterface $output, object $object, array $options = []): void; + } +diff --git a/src/Symfony/Component/Console/EventListener/ErrorListener.php b/src/Symfony/Component/Console/EventListener/ErrorListener.php +index 9925a5f746..e72fb5fc89 100644 +--- a/src/Symfony/Component/Console/EventListener/ErrorListener.php ++++ b/src/Symfony/Component/Console/EventListener/ErrorListener.php +@@ -35,5 +35,5 @@ class ErrorListener implements EventSubscriberInterface + * @return void + */ +- public function onConsoleError(ConsoleErrorEvent $event) ++ public function onConsoleError(ConsoleErrorEvent $event): void + { + if (null === $this->logger) { +@@ -55,5 +55,5 @@ class ErrorListener implements EventSubscriberInterface + * @return void + */ +- public function onConsoleTerminate(ConsoleTerminateEvent $event) ++ public function onConsoleTerminate(ConsoleTerminateEvent $event): void + { + if (null === $this->logger) { +diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatter.php b/src/Symfony/Component/Console/Formatter/OutputFormatter.php +index 9cb6310484..327aa3671c 100644 +--- a/src/Symfony/Component/Console/Formatter/OutputFormatter.php ++++ b/src/Symfony/Component/Console/Formatter/OutputFormatter.php +@@ -85,5 +85,5 @@ class OutputFormatter implements WrappableOutputFormatterInterface + * @return void + */ +- public function setDecorated(bool $decorated) ++ public function setDecorated(bool $decorated): void + { + $this->decorated = $decorated; +@@ -98,5 +98,5 @@ class OutputFormatter implements WrappableOutputFormatterInterface + * @return void + */ +- public function setStyle(string $name, OutputFormatterStyleInterface $style) ++ public function setStyle(string $name, OutputFormatterStyleInterface $style): void + { + $this->styles[strtolower($name)] = $style; +@@ -125,5 +125,5 @@ class OutputFormatter implements WrappableOutputFormatterInterface + * @return string + */ +- public function formatAndWrap(?string $message, int $width) ++ public function formatAndWrap(?string $message, int $width): string + { + if (null === $message) { +diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatterInterface.php b/src/Symfony/Component/Console/Formatter/OutputFormatterInterface.php +index 433cd41978..02187a7ffc 100644 +--- a/src/Symfony/Component/Console/Formatter/OutputFormatterInterface.php ++++ b/src/Symfony/Component/Console/Formatter/OutputFormatterInterface.php +@@ -24,5 +24,5 @@ interface OutputFormatterInterface + * @return void + */ +- public function setDecorated(bool $decorated); ++ public function setDecorated(bool $decorated): void; + + /** +@@ -36,5 +36,5 @@ interface OutputFormatterInterface + * @return void + */ +- public function setStyle(string $name, OutputFormatterStyleInterface $style); ++ public function setStyle(string $name, OutputFormatterStyleInterface $style): void; + + /** +diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php b/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php +index 346a474c61..db3012ced8 100644 +--- a/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php ++++ b/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php +@@ -42,5 +42,5 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface + * @return void + */ +- public function setForeground(string $color = null) ++ public function setForeground(string $color = null): void + { + if (1 > \func_num_args()) { +@@ -53,5 +53,5 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface + * @return void + */ +- public function setBackground(string $color = null) ++ public function setBackground(string $color = null): void + { + if (1 > \func_num_args()) { +@@ -69,5 +69,5 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface + * @return void + */ +- public function setOption(string $option) ++ public function setOption(string $option): void + { + $this->options[] = $option; +@@ -78,5 +78,5 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface + * @return void + */ +- public function unsetOption(string $option) ++ public function unsetOption(string $option): void + { + $pos = array_search($option, $this->options); +@@ -91,5 +91,5 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface + * @return void + */ +- public function setOptions(array $options) ++ public function setOptions(array $options): void + { + $this->color = new Color($this->foreground, $this->background, $this->options = $options); +diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php b/src/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php +index 3b15098cbe..3f850e129b 100644 +--- a/src/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php ++++ b/src/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php +@@ -24,5 +24,5 @@ interface OutputFormatterStyleInterface + * @return void + */ +- public function setForeground(?string $color); ++ public function setForeground(?string $color): void; + + /** +@@ -31,5 +31,5 @@ interface OutputFormatterStyleInterface + * @return void + */ +- public function setBackground(?string $color); ++ public function setBackground(?string $color): void; + + /** +@@ -38,5 +38,5 @@ interface OutputFormatterStyleInterface + * @return void + */ +- public function setOption(string $option); ++ public function setOption(string $option): void; + + /** +@@ -45,5 +45,5 @@ interface OutputFormatterStyleInterface + * @return void + */ +- public function unsetOption(string $option); ++ public function unsetOption(string $option): void; + + /** +@@ -52,5 +52,5 @@ interface OutputFormatterStyleInterface + * @return void + */ +- public function setOptions(array $options); ++ public function setOptions(array $options): void; + + /** +diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php b/src/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php +index f98c2eff7c..5d9c2c246f 100644 +--- a/src/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php ++++ b/src/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php +@@ -38,5 +38,5 @@ class OutputFormatterStyleStack implements ResetInterface + * @return void + */ +- public function reset() ++ public function reset(): void + { + $this->styles = []; +@@ -48,5 +48,5 @@ class OutputFormatterStyleStack implements ResetInterface + * @return void + */ +- public function push(OutputFormatterStyleInterface $style) ++ public function push(OutputFormatterStyleInterface $style): void + { + $this->styles[] = $style; +diff --git a/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php b/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php +index 746cd27e79..52c61429cf 100644 +--- a/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php ++++ b/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php +@@ -24,4 +24,4 @@ interface WrappableOutputFormatterInterface extends OutputFormatterInterface + * @return string + */ +- public function formatAndWrap(?string $message, int $width); ++ public function formatAndWrap(?string $message, int $width): string; + } +diff --git a/src/Symfony/Component/Console/Helper/DescriptorHelper.php b/src/Symfony/Component/Console/Helper/DescriptorHelper.php +index eb32bce8fc..57edd56954 100644 +--- a/src/Symfony/Component/Console/Helper/DescriptorHelper.php ++++ b/src/Symfony/Component/Console/Helper/DescriptorHelper.php +@@ -55,5 +55,5 @@ class DescriptorHelper extends Helper + * @throws InvalidArgumentException when the given format is not supported + */ +- public function describe(OutputInterface $output, ?object $object, array $options = []) ++ public function describe(OutputInterface $output, ?object $object, array $options = []): void + { + $options = array_merge([ +diff --git a/src/Symfony/Component/Console/Helper/Helper.php b/src/Symfony/Component/Console/Helper/Helper.php +index 3631b30f69..5e4407a591 100644 +--- a/src/Symfony/Component/Console/Helper/Helper.php ++++ b/src/Symfony/Component/Console/Helper/Helper.php +@@ -27,5 +27,5 @@ abstract class Helper implements HelperInterface + * @return void + */ +- public function setHelperSet(HelperSet $helperSet = null) ++ public function setHelperSet(HelperSet $helperSet = null): void + { + if (1 > \func_num_args()) { +@@ -95,5 +95,5 @@ abstract class Helper implements HelperInterface + * @return string + */ +- public static function formatTime(int|float $secs) ++ public static function formatTime(int|float $secs): string + { + static $timeFormats = [ +@@ -127,5 +127,5 @@ abstract class Helper implements HelperInterface + * @return string + */ +- public static function formatMemory(int $memory) ++ public static function formatMemory(int $memory): string + { + if ($memory >= 1024 * 1024 * 1024) { +@@ -147,5 +147,5 @@ abstract class Helper implements HelperInterface + * @return string + */ +- public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string) ++ public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string): string + { + $isDecorated = $formatter->isDecorated(); +diff --git a/src/Symfony/Component/Console/Helper/HelperInterface.php b/src/Symfony/Component/Console/Helper/HelperInterface.php +index ab626c9385..d9d069d3e5 100644 +--- a/src/Symfony/Component/Console/Helper/HelperInterface.php ++++ b/src/Symfony/Component/Console/Helper/HelperInterface.php +@@ -24,5 +24,5 @@ interface HelperInterface + * @return void + */ +- public function setHelperSet(?HelperSet $helperSet); ++ public function setHelperSet(?HelperSet $helperSet): void; + + /** +@@ -36,4 +36,4 @@ interface HelperInterface + * @return string + */ +- public function getName(); ++ public function getName(): string; + } +diff --git a/src/Symfony/Component/Console/Helper/HelperSet.php b/src/Symfony/Component/Console/Helper/HelperSet.php +index dc5d499caa..e19192da81 100644 +--- a/src/Symfony/Component/Console/Helper/HelperSet.php ++++ b/src/Symfony/Component/Console/Helper/HelperSet.php +@@ -39,5 +39,5 @@ class HelperSet implements \IteratorAggregate + * @return void + */ +- public function set(HelperInterface $helper, string $alias = null) ++ public function set(HelperInterface $helper, string $alias = null): void + { + $this->helpers[$helper->getName()] = $helper; +diff --git a/src/Symfony/Component/Console/Helper/InputAwareHelper.php b/src/Symfony/Component/Console/Helper/InputAwareHelper.php +index 6f8225973c..5f3640c346 100644 +--- a/src/Symfony/Component/Console/Helper/InputAwareHelper.php ++++ b/src/Symfony/Component/Console/Helper/InputAwareHelper.php +@@ -27,5 +27,5 @@ abstract class InputAwareHelper extends Helper implements InputAwareInterface + * @return void + */ +- public function setInput(InputInterface $input) ++ public function setInput(InputInterface $input): void + { + $this->input = $input; +diff --git a/src/Symfony/Component/Console/Helper/ProgressIndicator.php b/src/Symfony/Component/Console/Helper/ProgressIndicator.php +index 84dbef950c..5a38c8c28a 100644 +--- a/src/Symfony/Component/Console/Helper/ProgressIndicator.php ++++ b/src/Symfony/Component/Console/Helper/ProgressIndicator.php +@@ -74,5 +74,5 @@ class ProgressIndicator + * @return void + */ +- public function setMessage(?string $message) ++ public function setMessage(?string $message): void + { + $this->message = $message; +@@ -86,5 +86,5 @@ class ProgressIndicator + * @return void + */ +- public function start(string $message) ++ public function start(string $message): void + { + if ($this->started) { +@@ -106,5 +106,5 @@ class ProgressIndicator + * @return void + */ +- public function advance() ++ public function advance(): void + { + if (!$this->started) { +@@ -133,5 +133,5 @@ class ProgressIndicator + * @return void + 8000 */ +- public function finish(string $message) ++ public function finish(string $message): void + { + if (!$this->started) { +@@ -160,5 +160,5 @@ class ProgressIndicator + * @return void + */ +- public static function setPlaceholderFormatterDefinition(string $name, callable $callable) ++ public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void + { + self::$formatters ??= self::initPlaceholderFormatters(); +diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php +index 40c6a65663..8a65738606 100644 +--- a/src/Symfony/Component/Console/Helper/QuestionHelper.php ++++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php +@@ -93,5 +93,5 @@ class QuestionHelper extends Helper + * @return void + */ +- public static function disableStty() ++ public static function disableStty(): void + { + self::$stty = false; +@@ -183,5 +183,5 @@ class QuestionHelper extends Helper + * @return void + */ +- protected function writePrompt(OutputInterface $output, Question $question) ++ protected function writePrompt(OutputInterface $output, Question $question): void + { + $message = $question->getQuestion(); +@@ -221,5 +221,5 @@ class QuestionHelper extends Helper + * @return void + */ +- protected function writeError(OutputInterface $output, \Exception $error) ++ protected function writeError(OutputInterface $output, \Exception $error): void + { + if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { +diff --git a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php +index 8ebc84376b..4af2691707 100644 +--- a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php ++++ b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php +@@ -29,5 +29,5 @@ class SymfonyQuestionHelper extends QuestionHelper + * @return void + */ +- protected function writePrompt(OutputInterface $output, Question $question) ++ protected function writePrompt(OutputInterface $output, Question $question): void + { + $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); +@@ -87,5 +87,5 @@ class SymfonyQuestionHelper extends QuestionHelper + * @return void + */ +- protected function writeError(OutputInterface $output, \Exception $error) ++ protected function writeError(OutputInterface $output, \Exception $error): void + { + if ($output instanceof SymfonyStyle) { +diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php +index cf714873f5..e16ffaf97d 100644 +--- a/src/Symfony/Component/Console/Helper/Table.php ++++ b/src/Symfony/Component/Console/Helper/Table.php +@@ -70,5 +70,5 @@ class Table + * @return void + */ +- public static function setStyleDefinition(string $name, TableStyle $style) ++ public static function setStyleDefinition(string $name, TableStyle $style): void + { + self::$styles ??= self::initStyles(); +@@ -195,5 +195,5 @@ class Table + * @return $this + */ +- public function setRows(array $rows) ++ public function setRows(array $rows): static + { + $this->rows = []; +@@ -316,5 +316,5 @@ class Table + * @return void + */ +- public function render() ++ public function render(): void + { + $divider = new TableSeparator(); +diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php +index 59f9217ec5..77b402b7d7 100644 +--- a/src/Symfony/Component/Console/Input/ArgvInput.php ++++ b/src/Symfony/Component/Console/Input/ArgvInput.php +@@ -59,5 +59,5 @@ class ArgvInput extends Input + * @return void + */ +- protected function setTokens(array $tokens) ++ protected function setTokens(array $tokens): void + { + $this->tokens = $tokens; +@@ -67,5 +67,5 @@ class ArgvInput extends Input + * @return void + */ +- protected function parse() ++ protected function parse(): void + { + $parseOptions = true; +diff --git a/src/Symfony/Component/Console/Input/ArrayInput.php b/src/Symfony/Component/Console/Input/ArrayInput.php +index 355de61dd6..c6b82a2886 100644 +--- a/src/Symfony/Component/Console/Input/ArrayInput.php ++++ b/src/Symfony/Component/Console/Input/ArrayInput.php +@@ -117,5 +117,5 @@ class ArrayInput extends Input + * @return void + */ +- protected function parse() ++ protected function parse(): void + { + foreach ($this->parameters as $key => $value) { +diff --git a/src/Symfony/Component/Console/Input/Input.php b/src/Symfony/Component/Console/Input/Input.php +index 0f5617cd17..bdd5dd264f 100644 +--- a/src/Symfony/Component/Console/Input/Input.php ++++ b/src/Symfony/Component/Console/Input/Input.php +@@ -47,5 +47,5 @@ abstract class Input implements InputInterface, StreamableInputInterface + * @return void + */ +- public function bind(InputDefinition $definition) ++ public function bind(InputDefinition $definition): void + { + $this->arguments = []; +@@ -61,10 +61,10 @@ abstract class Input implements InputInterface, StreamableInputInterface + * @return void + */ +- abstract protected function parse(); ++ abstract protected function parse(): void; + + /** + * @return void + */ +- public function validate() ++ public function validate(): void + { + $definition = $this->definition; +@@ -86,5 +86,5 @@ abstract class Input implements InputInterface, StreamableInputInterface + * @return void + */ +- public function setInteractive(bool $interactive) ++ public function setInteractive(bool $interactive): void + { + $this->interactive = $interactive; +@@ -108,5 +108,5 @@ abstract class Input implements InputInterface, StreamableInputInterface + * @return void + */ +- public function setArgument(string $name, mixed $value) ++ public function setArgument(string $name, mixed $value): void + { + if (!$this->definition->hasArgument($name)) { +@@ -147,5 +147,5 @@ abstract class Input implements InputInterface, StreamableInputInterface + * @return void + */ +- public function setOption(string $name, mixed $value) ++ public function setOption(string $name, mixed $value): void + { + if ($this->definition->hasNegation($name)) { +@@ -178,5 +178,5 @@ abstract class Input implements InputInterface, StreamableInputInterface + * @return void + */ +- public function setStream($stream) ++ public function setStream($stream): void + { + $this->stream = $stream; +diff --git a/src/Symfony/Component/Console/Input/InputArgument.php b/src/Symfony/Component/Console/Input/InputArgument.php +index 0db0c7709b..5fbca28f99 100644 +--- a/src/Symfony/Component/Console/Input/InputArgument.php ++++ b/src/Symfony/Component/Console/Input/InputArgument.php +@@ -96,5 +96,5 @@ class InputArgument + * @throws LogicException When incorrect default value is given + */ +- public function setDefault(string|bool|int|float|array $default = null) ++ public function setDefault(string|bool|int|float|array $default = null): void + { + if (1 > \func_num_args()) { +diff --git a/src/Symfony/Component/Console/Input/InputAwareInterface.php b/src/Symfony/Component/Console/Input/InputAwareInterface.php +index 0ad27b4558..f5e544930e 100644 +--- a/src/Symfony/Component/Console/Input/InputAwareInterface.php ++++ b/src/Symfony/Component/Console/Input/InputAwareInterface.php +@@ -25,4 +25,4 @@ interface InputAwareInterface + * @return void + */ +- public function setInput(InputInterface $input); ++ public function setInput(InputInterface $input): void; + } +diff --git a/src/Symfony/Component/Console/Input/InputDefinition.php b/src/Symfony/Component/Console/Input/InputDefinition.php +index b7162d7706..3d41be7340 100644 +--- a/src/Symfony/Component/Console/Input/InputDefinition.php ++++ b/src/Symfony/Component/Console/Input/InputDefinition.php +@@ -50,5 +50,5 @@ class InputDefinition + * @return void + */ +- public function setDefinition(array $definition) ++ public function setDefinition(array $definition): void + { + $arguments = []; +@@ -73,5 +73,5 @@ class InputDefinition + * @return void + */ +- public function setArguments(array $arguments = []) ++ public function setArguments(array $arguments = []): void + { + $this->arguments = []; +@@ -89,5 +89,5 @@ class InputDefinition + * @return void + */ +- public function addArguments(?array $arguments = []) ++ public function addArguments(?array $arguments = []): void + { + if (null !== $arguments) { +@@ -103,5 +103,5 @@ class InputDefinition + * @throws LogicException When incorrect argument is given + */ +- public function addArgument(InputArgument $argument) ++ public function addArgument(InputArgument $argument): void + { + if (isset($this->arguments[$argument->getName()])) { +@@ -202,5 +202,5 @@ class InputDefinition + * @return void + */ +- public function setOptions(array $options = []) ++ public function setOptions(array $options = []): void + { + $this->options = []; +@@ -217,5 +217,5 @@ class InputDefinition + * @return void + */ +- public function addOptions(array $options = []) ++ public function addOptions(array $options = []): void + { + foreach ($options as $option) { +@@ -229,5 +229,5 @@ class InputDefinition + * @throws LogicException When option given already exist + */ +- public function addOption(InputOption $option) ++ public function addOption(InputOption $option): void + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { +diff --git a/src/Symfony/Component/Console/Input/InputInterface.php b/src/Symfony/Component/Console/Input/InputInterface.php +index aaed5fd01d..e7de9bcdec 100644 +--- a/src/Symfony/Component/Console/Input/InputInterface.php ++++ b/src/Symfony/Component/Console/Input/InputInterface.php +@@ -57,5 +57,5 @@ interface InputInterface + * @return mixed + */ +- public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false); ++ public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed; + + /** +@@ -66,5 +66,5 @@ interface InputInterface + * @throws RuntimeException + */ +- public function bind(InputDefinition $definition); ++ public function bind(InputDefinition $definition): void; + + /** +@@ -75,5 +75,5 @@ interface InputInterface + * @throws RuntimeException When not enough arguments are given + */ +- public function validate(); ++ public function validate(): void; + + /** +@@ -91,5 +91,5 @@ interface InputInterface + * @throws InvalidArgumentException When argument given doesn't exist + */ +- public function getArgument(string $name); ++ public function getArgument(string $name): mixed; + + /** +@@ -100,5 +100,5 @@ interface InputInterface + * @throws InvalidArgumentException When argument given doesn't exist + */ +- public function setArgument(string $name, mixed $value); ++ public function setArgument(string $name, mixed $value): void; + + /** +@@ -121,5 +121,5 @@ interface InputInterface + * @throws InvalidArgumentException When option given doesn't exist + */ +- public function getOption(string $name); ++ public function getOption(string $name): mixed; + + /** +@@ -130,5 +130,5 @@ interface InputInterface + * @throws InvalidArgumentException When option given doesn't exist + */ +- public function setOption(string $name, mixed $value); ++ public function setOption(string $name, mixed $value): void; + + /** +@@ -147,4 +147,4 @@ interface InputInterface + * @return void + */ +- public function setInteractive(bool $interactive); ++ public function setInteractive(bool $interactive): void; + } +diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php +index fdf88dcc27..cf7a71a3c4 100644 +--- a/src/Symfony/Component/Console/Input/InputOption.php ++++ b/src/Symfony/Component/Console/Input/InputOption.php +@@ -182,5 +182,5 @@ class InputOption + * @return void + */ +- public function setDefault(string|bool|int|float|array $default = null) ++ public function setDefault(string|bool|int|float|array $default = null): void + { + if (1 > \func_num_args()) { +diff --git a/src/Symfony/Component/Console/Input/StreamableInputInterface.php b/src/Symfony/Component/Console/Input/StreamableInputInterface.php +index 4b95fcb11e..b95fab2601 100644 +--- a/src/Symfony/Component/Console/Input/StreamableInputInterface.php ++++ b/src/Symfony/Component/Console/Input/StreamableInputInterface.php +@@ -29,5 +29,5 @@ interface StreamableInputInterface extends InputInterface + * @return void + */ +- public function setStream($stream); ++ public function setStream($stream): void; + + /** +diff --git a/src/Symfony/Component/Console/Output/BufferedOutput.php b/src/Symfony/Component/Console/Output/BufferedOutput.php +index ef5099bfd6..8fb59d794d 100644 +--- a/src/Symfony/Component/Console/Output/BufferedOutput.php ++++ b/src/Symfony/Component/Console/Output/BufferedOutput.php +@@ -33,5 +33,5 @@ class BufferedOutput extends Output + * @return void + */ +- protected function doWrite(string $message, bool $newline) ++ protected function doWrite(string $message, bool $newline): void + { + $this->buffer .= $message; +diff --git a/src/Symfony/Component/Console/Output/ConsoleOutput.php b/src/Symfony/Component/Console/Output/ConsoleOutput.php +index c1eb7cd14b..c7fc040bb4 100644 +--- a/src/Symfony/Component/Console/Output/ConsoleOutput.php ++++ b/src/Symfony/Component/Console/Output/ConsoleOutput.php +@@ -68,5 +68,5 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface + * @return void + */ +- public function setDecorated(bool $decorated) ++ public function setDecorated(bool $decorated): void + { + parent::setDecorated($decorated); +@@ -77,5 +77,5 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface + * @return void + */ +- public function setFormatter(OutputFormatterInterface $formatter) ++ public function setFormatter(OutputFormatterInterface $formatter): void + { + parent::setFormatter($formatter); +@@ -86,5 +86,5 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface + * @return void + */ +- public function setVerbosity(int $level) ++ public function setVerbosity(int $level): void + { + parent::setVerbosity($level); +@@ -100,5 +100,5 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface + * @return void + */ +- public function setErrorOutput(OutputInterface $error) ++ public function setErrorOutput(OutputInterface $error): void + { + $this->stderr = $error; +diff --git a/src/Symfony/Component/Console/Output/ConsoleOutputInterface.php b/src/Symfony/Component/Console/Output/ConsoleOutputInterface.php +index 9c0049c8f9..6ab9a753d5 100644 +--- a/src/Symfony/Component/Console/Output/ConsoleOutputInterface.php ++++ b/src/Symfony/Component/Console/Output/ConsoleOutputInterface.php +@@ -28,5 +28,5 @@ interface ConsoleOutputInterface extends OutputInterface + * @return void + */ +- public function setErrorOutput(OutputInterface $error); ++ public function setErrorOutput(OutputInterface $error): void; + + public function section(): ConsoleSectionOutput; +diff --git a/src/Symfony/Component/Console/Output/ConsoleSectionOutput.php b/src/Symfony/Component/Console/Output/ConsoleSectionOutput.php +index 3f3f1434be..594880b9e3 100644 +--- a/src/Symfony/Component/Console/Output/ConsoleSectionOutput.php ++++ b/src/Symfony/Component/Console/Output/ConsoleSectionOutput.php +@@ -64,5 +64,5 @@ class ConsoleSectionOutput extends StreamOutput + * @return void + */ +- public function clear(int $lines = null) ++ public function clear(int $lines = null): void + { + if (empty($this->content) || !$this->isDecorated()) { +@@ -87,5 +87,5 @@ class ConsoleSectionOutput extends StreamOutput + * @return void + */ +- public function overwrite(string|iterable $message) ++ public function overwrite(string|iterable $message): void + { + $this->clear(); +@@ -167,5 +167,5 @@ class ConsoleSectionOutput extends StreamOutput + * @return void + */ +- protected function doWrite(string $message, bool $newline) ++ protected function doWrite(string $message, bool $newline): void + { + if (!$this->isDecorated()) { +diff --git a/src/Symfony/Component/Console/Output/NullOutput.php b/src/Symfony/Component/Console/Output/NullOutput.php +index f3aa15b1d4..206df78910 100644 +--- a/src/Symfony/Component/Console/Output/NullOutput.php ++++ b/src/Symfony/Component/Console/Output/NullOutput.php +@@ -30,5 +30,5 @@ class NullOutput implements OutputInterface + * @return void + */ +- public function setFormatter(OutputFormatterInterface $formatter) ++ public function setFormatter(OutputFormatterInterface $formatter): void + { + // do nothing +@@ -44,5 +44,5 @@ class NullOutput implements OutputInterface + * @return void + */ +- public function setDecorated(bool $decorated) ++ public function setDecorated(bool $decorated): void + { + // do nothing +@@ -57,5 +57,5 @@ class NullOutput implements OutputInterface + * @return void + */ +- public function setVerbosity(int $level) ++ public function setVerbosity(int $level): void + { + // do nothing +@@ -90,5 +90,5 @@ class NullOutput implements OutputInterface + * @return void + */ +- public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL) ++ public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL): void + { + // do nothing +@@ -98,5 +98,5 @@ class NullOutput implements OutputInterface + * @return void + */ +- public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) ++ public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL): void + { + // do nothing +diff --git a/src/Symfony/Component/Console/Output/Output.php b/src/Symfony/Component/Console/Output/Output.php +index 3a06311a8b..8204b8a744 100644 +--- a/src/Symfony/Component/Console/Output/Output.php ++++ b/src/Symfony/Component/Console/Output/Output.php +@@ -48,5 +48,5 @@ abstract class Output implements OutputInterface + * @return void + */ +- public function setFormatter(OutputFormatterInterface $formatter) ++ public function setFormatter(OutputFormatterInterface $formatter): void + { + $this->formatter = $formatter; +@@ -61,5 +61,5 @@ abstract class Output implements OutputInterface + * @return void + */ +- public function setDecorated(bool $decorated) ++ public function setDecorated(bool $decorated): void + { + $this->formatter->setDecorated($decorated); +@@ -74,5 +74,5 @@ abstract class Output implements OutputInterface + * @return void + */ +- public function setVerbosity(int $level) ++ public function setVerbosity(int $level): void + { + $this->verbosity = $level; +@@ -107,5 +107,5 @@ abstract class Output implements OutputInterface + * @return void + */ +- public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL) ++ public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL): void + { + $this->write($messages, true, $options); +@@ -115,5 +115,5 @@ abstract class Output implements OutputInterface + * @return void + */ +- public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) ++ public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL): void + { + if (!is_iterable($messages)) { +@@ -152,4 +152,4 @@ abstract class Output implements OutputInterface + * @return void + */ +- abstract protected function doWrite(string $message, bool $newline); ++ abstract protected function doWrite(string $message, bool $newline): void; + } +diff --git a/src/Symfony/Component/Console/Output/OutputInterface.php b/src/Symfony/Component/Console/Output/OutputInterface.php +index fb1557720f..8b31dee20c 100644 +--- a/src/Symfony/Component/Console/Output/OutputInterface.php ++++ b/src/Symfony/Component/Console/Output/OutputInterface.php +@@ -40,5 +40,5 @@ interface OutputInterface + * @return void + */ +- public function write(string|iterable $messages, bool $newline = false, int $options = 0); ++ public function write(string|iterable $messages, bool $newline = false, int $options = 0): void; + + /** +@@ -50,5 +50,5 @@ interface OutputInterface + * @return void + */ +- public function writeln(string|iterable $messages, int $options = 0); ++ public function writeln(string|iterable $messages, int $options = 0): void; + + /** +@@ -57,5 +57,5 @@ interface OutputInterface + * @return void + */ +- public function setVerbosity(int $level); ++ public function setVerbosity(int $level): void; + + /** +@@ -89,5 +89,5 @@ interface OutputInterface + * @return void + */ +- public function setDecorated(bool $decorated); ++ public function setDecorated(bool $decorated): void; + + /** +@@ -99,5 +99,5 @@ interface OutputInterface + * @return void + */ +- public function setFormatter(OutputFormatterInterface $formatter); ++ public function setFormatter(OutputFormatterInterface $formatter): void; + + /** +diff --git a/src/Symfony/Component/Console/Output/StreamOutput.php b/src/Symfony/Component/Console/Output/StreamOutput.php +index 155066ea0e..85e07025bc 100644 +--- a/src/Symfony/Component/Console/Output/StreamOutput.php ++++ b/src/Symfony/Component/Console/Output/StreamOutput.php +@@ -66,5 +66,5 @@ class StreamOutput extends Output + * @return void + */ +- protected function doWrite(string $message, bool $newline) ++ protected function doWrite(string $message, bool $newline): void + { + if ($newline) { +diff --git a/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php b/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php +index b00445ece8..5e9b1086b0 100644 +--- a/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php ++++ b/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php +@@ -49,5 +49,5 @@ class TrimmedBufferOutput extends Output + * @return void + */ +- protected function doWrite(string $message, bool $newline) ++ protected function doWrite(string $message, bool $newline): void + { + $this->buffer .= $message; +diff --git a/src/Symfony/Component/Console/Question/Question.php b/src/Symfony/Component/Console/Question/Question.php +index 26896bb531..af97d04ddc 100644 +--- a/src/Symfony/Component/Console/Question/Question.php ++++ b/src/Symfony/Component/Console/Question/Question.php +@@ -270,5 +270,5 @@ class Question + * @return bool + */ +- protected function isAssoc(array $array) ++ protected function isAssoc(array $array): bool + { + return (bool) \count(array_filter(array_keys($array), 'is_string')); +diff --git a/src/Symfony/Component/Console/Style/OutputStyle.php b/src/Symfony/Component/Console/Style/OutputStyle.php +index ddfa8decc2..e67453d9fe 100644 +--- a/src/Symfony/Component/Console/Style/OutputStyle.php ++++ b/src/Symfony/Component/Console/Style/OutputStyle.php +@@ -34,5 +34,5 @@ abstract class OutputStyle implements OutputInterface, StyleInterface + * @return void + */ +- public function newLine(int $count = 1) ++ public function newLine(int $count = 1): void + { + $this->output->write(str_repeat(\PHP_EOL, $count)); +@@ -47,5 +47,5 @@ abstract class OutputStyle implements OutputInterface, StyleInterface + * @return void + */ +- public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) ++ public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL): void + { + $this->output->write($messages, $newline, $type); +@@ -55,5 +55,5 @@ abstract class OutputStyle implements OutputInterface, StyleInterface + * @return void + */ +- public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) ++ public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void + { + $this->output->writeln($messages, $type); +@@ -63,5 +63,5 @@ abstract class OutputStyle implements OutputInterface, StyleInterface + * @return void + */ +- public function setVerbosity(int $level) ++ public function setVerbosity(int $level): void + { + $this->output->setVerbosity($level); +@@ -76,5 +76,5 @@ abstract class OutputStyle implements OutputInterface, StyleInterface + * @return void + */ +- public function setDecorated(bool $decorated) ++ public function setDecorated(bool $decorated): void + { + $this->output->setDecorated($decorated); +@@ -89,5 +89,5 @@ abstract class OutputStyle implements OutputInterface, StyleInterface + * @return void + */ +- public function setFormatter(OutputFormatterInterface $formatter) ++ public function setFormatter(OutputFormatterInterface $formatter): void + { + $this->output->setFormatter($formatter); +@@ -122,5 +122,5 @@ abstract class OutputStyle implements OutputInterface, StyleInterface + * @return OutputInterface + */ +- protected function getErrorOutput() ++ protected function getErrorOutput(): OutputInterface + { + if (!$this->output instanceof ConsoleOutputInterface) { +diff --git a/src/Symfony/Component/Console/Style/StyleInterface.php b/src/Symfony/Component/Console/Style/StyleInterface.php +index e25a65bd24..1d4bb7fe71 100644 +--- a/src/Symfony/Component/Console/Style/StyleInterface.php ++++ b/src/Symfony/Component/Console/Style/StyleInterface.php +@@ -24,5 +24,5 @@ interface StyleInterface + * @return void + */ +- public function title(string $message); ++ public function title(string $message): void; + + /** +@@ -31,5 +31,5 @@ interface StyleInterface + * @return void + */ +- public function section(string $message); ++ public function section(string $message): void; + + /** +@@ -38,5 +38,5 @@ interface StyleInterface + * @return void + */ +- public function listing(array $elements); ++ public function listing(array $elements): void; + + /** +@@ -45,5 +45,5 @@ interface StyleInterface + * @return void + */ +- public function text(string|array $message); ++ public function text(string|array $message): void; + + /** +@@ -52,5 +52,5 @@ interface StyleInterface + * @return void + */ +- public function success(string|array $message); ++ public function success(string|array $message): void; + + /** +@@ -59,5 +59,5 @@ interface StyleInterface + * @return void + */ +- public function error(string|array $message); ++ public function error(string|array $message): void; + + /** +@@ -66,5 +66,5 @@ interface StyleInterface + * @return void + */ +- public function warning(string|array $message); ++ public function warning(string|array $message): void; + + /** +@@ -73,5 +73,5 @@ interface StyleInterface + * @return void + */ +- public function note(string|array $message); ++ public function note(string|array $message): void; + + /** +@@ -80,5 +80,5 @@ interface StyleInterface + * @return void + */ +- public function caution(string|array $message); ++ public function caution(string|array $message): void; + + /** +@@ -87,5 +87,5 @@ interface StyleInterface + * @return void + */ +- public function table(array $headers, array $rows); ++ public function table(array $headers, array $rows): void; + + /** +@@ -114,5 +114,5 @@ interface StyleInterface + * @return void + */ +- public function newLine(int $count = 1); ++ public function newLine(int $count = 1): void; + + /** +@@ -121,5 +121,5 @@ interface StyleInterface + * @return void + */ +- public function progressStart(int $max = 0); ++ public function progressStart(int $max = 0): void; + + /** +@@ -128,5 +128,5 @@ interface StyleInterface + * @return void + */ +- public function progressAdvance(int $step = 1); ++ public function progressAdvance(int $step = 1): void; + + /** +@@ -135,4 +135,4 @@ interface StyleInterface + * @return void + */ +- public function progressFinish(); ++ public function progressFinish(): void; + } +diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php +index cecce6c01b..f2e0c7fdf5 100644 +--- a/src/Symfony/Component/Console/Style/SymfonyStyle.php ++++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php +@@ -64,5 +64,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function block(string|array $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true) ++ public function block(string|array $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true): void + { + $messages = \is_array($messages) ? array_values($messages) : [$messages]; +@@ -76,5 +76,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function title(string $message) ++ public function title(string $message): void + { + $this->autoPrependBlock(); +@@ -89,5 +89,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function section(string $message) ++ public function section(string $message): void + { + $this->autoPrependBlock(); +@@ -102,5 +102,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function listing(array $elements) ++ public function listing(array $elements): void + { + $this->autoPrependText(); +@@ -114,5 +114,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function text(string|array $message) ++ public function text(string|array $message): void + { + $this->autoPrependText(); +@@ -129,5 +129,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function comment(string|array $message) ++ public function comment(string|array $message): void + { + $this->block($message, null, null, ' // ', false, false); +@@ -137,5 +137,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function success(string|array $message) ++ public function success(string|array $message): void + { + $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); +@@ -145,5 +145,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function error(string|array $message) ++ public function error(string|array $message): void + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); +@@ -153,5 +153,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function warning(string|array $message) ++ public function warning(string|array $message): void + { + $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); +@@ -161,5 +161,5 @@ class SymfonyStyle extends 8000 OutputStyle + * @return void + */ +- public function note(string|array $message) ++ public function note(string|array $message): void + { + $this->block($message, 'NOTE', 'fg=yellow', ' ! '); +@@ -171,5 +171,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function info(string|array $message) ++ public function info(string|array $message): void + { + $this->block($message, 'INFO', 'fg=green', ' ', true); +@@ -179,5 +179,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function caution(string|array $message) ++ public function caution(string|array $message): void + { + $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); +@@ -187,5 +187,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function table(array $headers, array $rows) ++ public function table(array $headers, array $rows): void + { + $this->createTable() +@@ -203,5 +203,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function horizontalTable(array $headers, array $rows) ++ public function horizontalTable(array $headers, array $rows): void + { + $this->createTable() +@@ -225,5 +225,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function definitionList(string|array|TableSeparator ...$list) ++ public function definitionList(string|array|TableSeparator ...$list): void + { + $headers = []; +@@ -289,5 +289,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function progressStart(int $max = 0) ++ public function progressStart(int $max = 0): void + { + $this->progressBar = $this->createProgressBar($max); +@@ -298,5 +298,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function progressAdvance(int $step = 1) ++ public function progressAdvance(int $step = 1): void + { + $this->getProgressBar()->advance($step); +@@ -306,5 +306,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function progressFinish() ++ public function progressFinish(): void + { + $this->getProgressBar()->finish(); +@@ -362,5 +362,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) ++ public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void + { + if (!is_iterable($messages)) { +@@ -377,5 +377,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) ++ public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL): void + { + if (!is_iterable($messages)) { +@@ -392,5 +392,5 @@ class SymfonyStyle extends OutputStyle + * @return void + */ +- public function newLine(int $count = 1) ++ public function newLine(int $count = 1): void + { + parent::newLine($count); +diff --git a/src/Symfony/Component/Console/Tests/EventListener/ErrorListenerTest.php b/src/Symfony/Component/Console/Tests/EventListener/ErrorListenerTest.php +index 6ad89dc522..40020baee7 100644 +--- a/src/Symfony/Component/Console/Tests/EventListener/ErrorListenerTest.php ++++ b/src/Symfony/Component/Console/Tests/EventListener/ErrorListenerTest.php +@@ -141,5 +141,5 @@ class NonStringInput extends Input + } + +- public function parse() ++ public function parse(): void + { + } +diff --git a/src/Symfony/Component/Console/Tests/Output/OutputTest.php b/src/Symfony/Component/Console/Tests/Output/OutputTest.php +index f337c4ddd5..b7c0a98d9f 100644 +--- a/src/Symfony/Component/Console/Tests/Output/OutputTest.php ++++ b/src/Symfony/Component/Console/Tests/Output/OutputTest.php +@@ -183,5 +183,5 @@ class TestOutput extends Output + } + +- protected function doWrite(string $message, bool $newline) ++ protected function doWrite(string $message, bool $newline): void + { + $this->output .= $message.($newline ? "\n" : ''); +diff --git a/src/Symfony/Component/CssSelector/Parser/Reader.php b/src/Symfony/Component/CssSelector/Parser/Reader.php +index 7f6ae7a600..d79db02567 100644 +--- a/src/Symfony/Component/CssSelector/Parser/Reader.php ++++ b/src/Symfony/Component/CssSelector/Parser/Reader.php +@@ -57,5 +57,5 @@ class Reader + * @return int|false + */ +- public function getOffset(string $string): int|bool ++ public function getOffset(string $string): int|false + { + $position = strpos($this->source, $string, $this->position); +diff --git a/src/Symfony/Component/DependencyInjection/Argument/ArgumentInterface.php b/src/Symfony/Component/DependencyInjection/Argument/ArgumentInterface.php +index 3b39f36625..de2d7f2536 100644 +--- a/src/Symfony/Component/DependencyInjection/Argument/ArgumentInterface.php ++++ b/src/Symfony/Component/DependencyInjection/Argument/ArgumentInterface.php +@@ -24,4 +24,4 @@ interface ArgumentInterface + * @return void + */ +- public function setValues(array $values); ++ public function setValues(array $values): void; + } +diff --git a/src/Symfony/Component/DependencyInjection/Argument/IteratorArgument.php b/src/Symfony/Component/DependencyInjection/Argument/IteratorArgument.php +index aedd1e659e..92aff35b84 100644 +--- a/src/Symfony/Component/DependencyInjection/Argument/IteratorArgument.php ++++ b/src/Symfony/Component/DependencyInjection/Argument/IteratorArgument.php +@@ -34,5 +34,5 @@ class IteratorArgument implements ArgumentInterface + * @return void + */ +- public function setValues(array $values) ++ public function setValues(array $values): void + { + $this->values = $values; +diff --git a/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php b/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php +index be86412bcb..28f53536bc 100644 +--- a/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php ++++ b/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php +@@ -36,5 +36,5 @@ class ServiceClosureArgument implements ArgumentInterface + * @return void + */ +- public function setValues(array $values) ++ public function setValues(array $values): void + { + if ([0] !== array_keys($values)) { +diff --git a/src/Symfony/Component/DependencyInjection/Argument/ServiceLocatorArgument.php b/src/Symfony/Component/DependencyInjection/Argument/ServiceLocatorArgument.php +index de533fcca6..ed25852022 100644 +--- a/src/Symfony/Component/DependencyInjection/Argument/ServiceLocatorArgument.php ++++ b/src/Symfony/Component/DependencyInjection/Argument/ServiceLocatorArgument.php +@@ -45,5 +45,5 @@ class ServiceLocatorArgument implements ArgumentInterface + * @return void + */ +- public function setValues(array $values) ++ public function setValues(array $values): void + { + $this->values = $values; +diff --git a/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php b/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php +index b4e982c457..521a9531f8 100644 +--- a/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php ++++ b/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php +@@ -56,5 +56,5 @@ class TaggedIteratorArgument extends IteratorArgument + * @return string + */ +- public function getTag() ++ public function getTag(): string + { + return $this->tag; +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +index 95251dec82..74c2b38eb8 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +@@ -40,5 +40,5 @@ abstract class AbstractRecursivePass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $this->container = $container; +@@ -54,5 +54,5 @@ abstract class AbstractRecursivePass implements CompilerPassInterface + * @return void + */ +- protected function enableExpressionProcessing() ++ protected function enableExpressionProcessing(): void + { + $this->processExpressions = true; +@@ -74,5 +74,5 @@ abstract class AbstractRecursivePass implements CompilerPassInterface + * @return mixed + */ +- protected function processValue(mixed $value, bool $isRoot = false) ++ protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if (\is_array($value)) { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +index de033d9847..e515b6344c 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +@@ -58,5 +58,5 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $this->container = $container; +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutoAliasServicePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutoAliasServicePass.php +index 3f070dcc0c..aa0e5186bf 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/AutoAliasServicePass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/AutoAliasServicePass.php +@@ -24,5 +24,5 @@ class AutoAliasServicePass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + foreach ($container->findTaggedServiceIds('auto_alias') as $serviceId => $tags) { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +index 9d9250b9f9..4f78f4ddeb 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +@@ -62,5 +62,5 @@ class AutowirePass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + try { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php +index 1fb8935c3e..1cfccaa671 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php +@@ -35,5 +35,5 @@ class CheckCircularReferencesPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $graph = $container->getCompiler()->getServiceReferenceGraph(); +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php +index c62345f26e..098772e2ef 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php +@@ -38,5 +38,5 @@ class CheckDefinitionValidityPass implements CompilerPassInterface + * @throws RuntimeException When the Definition is invalid + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + foreach ($container->getDefinitions() as $id => $definition) { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php +index 8f828d3221..fb41bd49a3 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php +@@ -29,5 +29,5 @@ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $this->serviceLocatorContextIds = []; +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php b/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php +index c8cbccb4b9..0446970598 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php +@@ -45,5 +45,5 @@ class Compiler + * @return void + */ +- public function addPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) ++ public function addPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): void + { + $this->passConfig->addPass($pass, $type, $priority); +@@ -55,5 +55,5 @@ class Compiler + * @return void + */ +- public function log(CompilerPassInterface $pass, string $message) ++ public function log(CompilerPassInterface $pass, string $message): void + { + if (str_contains($message, "\n")) { +@@ -74,5 +74,5 @@ class Compiler + * @return void + */ +- public function compile(ContainerBuilder $container) ++ public function compile(ContainerBuilder $container): void + { + try { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CompilerPassInterface.php b/src/Symfony/Component/DependencyInjection/Compiler/CompilerPassInterface.php +index 2ad4a048ba..719267be1e 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/CompilerPassInterface.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/CompilerPassInterface.php +@@ -26,4 +26,4 @@ interface CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container); ++ public function process(ContainerBuilder $container): void; + } +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php +index c38bfa7744..10d999f093 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php +@@ -31,5 +31,5 @@ class DecoratorServicePass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $definitions = new \SplPriorityQueue(); +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ExtensionCompilerPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ExtensionCompilerPass.php +index 953b7f942e..96912701e5 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ExtensionCompilerPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ExtensionCompilerPass.php +@@ -25,5 +25,5 @@ class ExtensionCompilerPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + foreach ($container->getExtensions() as $extension) { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +index f4eb931412..0e5448aa91 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +@@ -41,5 +41,5 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $this->container = $container; +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php +index cd8ebfe0f7..36cd2b93a4 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php +@@ -33,5 +33,5 @@ class MergeExtensionConfigurationPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $parameters = $container->getParameterBag()->all(); +@@ -169,10 +169,10 @@ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder + } + +- public function registerExtension(ExtensionInterface $extension) ++ public function registerExtension(ExtensionInterface $extension): void + { + throw new LogicException(sprintf('You cannot register extension "%s" from "%s". Extensions must be registered before the container is compiled.', get_debug_type($extension), $this->extensionClass)); + } + +- public function compile(bool $resolveEnvPlaceholders = false) ++ public function compile(bool $resolveEnvPlaceholders = false): void + { + throw new LogicException(sprintf('Cannot compile the container in extension "%s".', $this->extensionClass)); +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +index 16b24cc710..c8296e7c1d 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +@@ -126,5 +126,5 @@ class PassConfig + * @throws InvalidArgumentException when a pass type doesn't exist + */ +- public function addPass(CompilerPassInterface $pass, string $type = self::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) ++ public function addPass(CompilerPassInterface $pass, string $type = self::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): void + { + $property = $type.'Passes'; +@@ -202,5 +202,5 @@ class PassConfig + * @return void + */ +- public function setMergePass(CompilerPassInterface $pass) ++ public function setMergePass(CompilerPassInterface $pass): void + { + $this->mergePass = $pass; +@@ -214,5 +214,5 @@ class PassConfig + * @return void + */ +- public function setAfterRemovingPasses(array $passes) ++ public function setAfterRemovingPasses(array $passes): void + { + $this->afterRemovingPasses = [$passes]; +@@ -226,5 +226,5 @@ class PassConfig + * @return void + */ +- public function setBeforeOptimizationPasses(array $passes) ++ public function setBeforeOptimizationPasses(array $passes): void + { + $this->beforeOptimizationPasses = [$passes]; +@@ -238,5 +238,5 @@ class PassConfig + * @return void + */ +- public function setBeforeRemovingPasses(array $passes) ++ public function setBeforeRemovingPasses(array $passes): void + { + $this->beforeRemovingPasses = [$passes]; +@@ -250,5 +250,5 @@ class PassConfig + * @return void + */ +- public function setOptimizationPasses(array $passes) ++ public function setOptimizationPasses(array $passes): void + { + $this->optimizationPasses = [$passes]; +@@ -262,5 +262,5 @@ class PassConfig + * @return void + */ +- public function setRemovingPasses(array $passes) ++ public function setRemovingPasses(array $passes): void + { + $this->removingPasses = [$passes]; +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterEnvVarProcessorsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterEnvVarProcessorsPass.php +index 2a706bfe5e..68f25a4ff8 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterEnvVarProcessorsPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterEnvVarProcessorsPass.php +@@ -31,5 +31,5 @@ class RegisterEnvVarProcessorsPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $bag = $container->getParameterBag(); +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterReverseContainerPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterReverseContainerPass.php +index aa4cca3571..4365ecffde 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterReverseContainerPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterReverseContainerPass.php +@@ -33,5 +33,5 @@ class RegisterReverseContainerPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('reverse_container')) { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RemoveAbstractDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RemoveAbstractDefinitionsPass.php +index d0ebfcc509..9b50d622aa 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/RemoveAbstractDefinitionsPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/RemoveAbstractDefinitionsPass.php +@@ -24,5 +24,5 @@ class RemoveAbstractDefinitionsPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + foreach ($container->getDefinitions() as $id => $definition) { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RemoveBuildParametersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RemoveBuildParametersPass.php +index 75e714475c..e5bb34a465 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/RemoveBuildParametersPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/RemoveBuildParametersPass.php +@@ -24,5 +24,5 @@ class RemoveBuildParametersPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $parameterBag = $container->getParameterBag(); +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RemovePrivateAliasesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RemovePrivateAliasesPass.php +index 93c3fd2672..250a640d63 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/RemovePrivateAliasesPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/RemovePrivateAliasesPass.php +@@ -28,5 +28,5 @@ class RemovePrivateAliasesPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + foreach ($container->getAliases() as $id => $alias) { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php +index df97a62f7e..60126d8d06 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php +@@ -30,5 +30,5 @@ class RemoveUnusedDefinitionsPass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + try { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php +index 808cde2081..83063d607d 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php +@@ -34,5 +34,5 @@ class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass + * @throws InvalidArgumentException if the service definition does not exist + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + // First collect all alias targets that need to be replaced +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php +index 55a358efdf..be943ae655 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php +@@ -36,5 +36,5 @@ class ResolveBindingsPass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $this->usedBindings = $container->getRemovedBindingIds(); +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveClassPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveClassPass.php +index 468837672e..bdfa98bfa6 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveClassPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveClassPass.php +@@ -24,5 +24,5 @@ class ResolveClassPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + foreach ($container->getDefinitions() as $id => $definition) { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDecoratorStackPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDecoratorStackPass.php +index da02622b21..395c5a1de6 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDecoratorStackPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDecoratorStackPass.php +@@ -28,5 +28,5 @@ class ResolveDecoratorStackPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $stacks = []; +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveHotPathPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveHotPathPass.php +index bffb9dab85..40e484f5a5 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveHotPathPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveHotPathPass.php +@@ -29,5 +29,5 @@ class ResolveHotPathPass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + try { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php +index 88d6fa01fd..d057c13802 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php +@@ -28,5 +28,5 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + foreach ($container->getAutoconfiguredInstanceof() as $interface => $definition) { +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php +index 7a2a69aa6a..7a265cc8aa 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php +@@ -39,5 +39,5 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $this->container = $container; +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveNoPreloadPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveNoPreloadPass.php +index 3302dd2cd2..459c22434b 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveNoPreloadPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveNoPreloadPass.php +@@ -30,5 +30,5 @@ class ResolveNoPreloadPass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $this->container = $container; +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php +index c4a1412ff2..2bb03c253e 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php +@@ -37,5 +37,5 @@ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass + * @throws ParameterNotFoundException + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $this->bag = $container->getParameterBag(); +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php +index 3176d405f8..0bbc25ba9e 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php +@@ -26,5 +26,5 @@ class ResolveReferencesToAliasesPass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + parent::process($container); +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php +index e7f42f87db..8c995103ad 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php +@@ -38,5 +38,5 @@ class ServiceReferenceGraphNode + * @return void + */ +- public function addInEdge(ServiceReferenceGraphEdge $edge) ++ 8000 public function addInEdge(ServiceReferenceGraphEdge $edge): void + { + $this->inEdges[] = $edge; +@@ -46,5 +46,5 @@ class ServiceReferenceGraphNode + * @return void + */ +- public function addOutEdge(ServiceReferenceGraphEdge $edge) ++ public function addOutEdge(ServiceReferenceGraphEdge $edge): void + { + $this->outEdges[] = $edge; +@@ -108,5 +108,5 @@ class ServiceReferenceGraphNode + * @return void + */ +- public function clear() ++ public function clear(): void + { + $this->inEdges = $this->outEdges = []; +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php +index 2d6542660b..20287f9286 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/ValidateEnvPlaceholdersPass.php +@@ -34,5 +34,5 @@ class ValidateEnvPlaceholdersPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $this->extensionConfig = []; +diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php +index 3ea2228b94..f1d7078383 100644 +--- a/src/Symfony/Component/DependencyInjection/Container.php ++++ b/src/Symfony/Component/DependencyInjection/Container.php +@@ -83,5 +83,5 @@ class Container implements ContainerInterface, ResetInterface + * @return void + */ +- public function compile() ++ public function compile(): void + { + $this->parameterBag->resolve(); +@@ -118,5 +118,5 @@ class Container implements ContainerInterface, ResetInterface + * @throws ParameterNotFoundException if the parameter is not defined + */ +- public function getParameter(string $name) ++ public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + return $this->parameterBag->get($name); +@@ -131,5 +131,5 @@ class Container implements ContainerInterface, ResetInterface + * @return void + */ +- public function setParameter(string $name, array|bool|string|int|float|\UnitEnum|null $value) ++ public function setParameter(string $name, array|bool|string|int|float|\UnitEnum|null $value): void + { + $this->parameterBag->set($name, $value); +@@ -144,5 +144,5 @@ class Container implements ContainerInterface, ResetInterface + * @return void + */ +- public function set(string $id, ?object $service) ++ public function set(string $id, ?object $service): void + { + // Runs the internal initializer; used by the dumped container to include always-needed files +@@ -287,5 +287,5 @@ class Container implements ContainerInterface, ResetInterface + * @return void + */ +- public function reset() ++ public function reset(): void + { + $services = $this->services + $this->privates; +@@ -342,5 +342,5 @@ class Container implements ContainerInterface, ResetInterface + * @return mixed + */ +- protected function load(string $file) ++ protected function load(string $file): mixed + { + return require $file; +diff --git a/src/Symfony/Component/DependencyInjection/ContainerAwareInterface.php b/src/Symfony/Component/DependencyInjection/ContainerAwareInterface.php +index 084a321ab5..09fb37f3aa 100644 +--- a/src/Symfony/Component/DependencyInjection/ContainerAwareInterface.php ++++ b/src/Symfony/Component/DependencyInjection/ContainerAwareInterface.php +@@ -24,4 +24,4 @@ interface ContainerAwareInterface + * @return void + */ +- public function setContainer(?ContainerInterface $container); ++ public function setContainer(?ContainerInterface $container): void; + } +diff --git a/src/Symfony/Component/DependencyInjection/ContainerAwareTrait.php b/src/Symfony/Component/DependencyInjection/ContainerAwareTrait.php +index ac67b468c5..bc1e395810 100644 +--- a/src/Symfony/Component/DependencyInjection/ContainerAwareTrait.php ++++ b/src/Symfony/Component/DependencyInjection/ContainerAwareTrait.php +@@ -27,5 +27,5 @@ trait ContainerAwareTrait + * @return void + */ +- public function setContainer(ContainerInterface $container = null) ++ public function setContainer(ContainerInterface $container = null): void + { + if (1 > \func_num_args()) { +diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +index 44df682ebc..e5957c6f57 100644 +--- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php ++++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +@@ -179,5 +179,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function setResourceTracking(bool $track) ++ public function setResourceTracking(bool $track): void + { + $this->trackResources = $track; +@@ -197,5 +197,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator) ++ public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator): void + { + $this->proxyInstantiator = $proxyInstantiator; +@@ -205,5 +205,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function registerExtension(ExtensionInterface $extension) ++ public function registerExtension(ExtensionInterface $extension): void + { + $this->extensions[$extension->getAlias()] = $extension; +@@ -487,5 +487,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @throws BadMethodCallException When this ContainerBuilder is compiled + */ +- public function set(string $id, ?object $service) ++ public function set(string $id, ?object $service): void + { + if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) { +@@ -504,5 +504,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function removeDefinition(string $id) ++ public function removeDefinition(string $id): void + { + if (isset($this->definitions[$id])) { +@@ -616,5 +616,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @throws BadMethodCallException When this ContainerBuilder is compiled + */ +- public function merge(self $container) ++ public function merge(self $container): void + { + if ($this->isCompiled()) { +@@ -708,5 +708,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function prependExtensionConfig(string $name, array $config) ++ public function prependExtensionConfig(string $name, array $config): void + { + if (!isset($this->extensionConfigs[$name])) { +@@ -752,5 +752,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function compile(bool $resolveEnvPlaceholders = false) ++ public function compile(bool $resolveEnvPlaceholders = false): void + { + $compiler = $this->getCompiler(); +@@ -816,5 +816,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function addAliases(array $aliases) ++ public function addAliases(array $aliases): void + { + foreach ($aliases as $alias => $id) { +@@ -830,5 +830,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function setAliases(array $aliases) ++ public function setAliases(array $aliases): void + { + $this->aliasDefinitions = []; +@@ -864,5 +864,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function removeAlias(string $alias) ++ public function removeAlias(string $alias): void + { + if (isset($this->aliasDefinitions[$alias])) { +@@ -926,5 +926,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function addDefinitions(array $definitions) ++ public function addDefinitions(array $definitions): void + { + foreach ($definitions as $id => $definition) { +@@ -940,5 +940,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function setDefinitions(array $definitions) ++ public function setDefinitions(array $definitions): void + { + $this->definitions = []; +@@ -1338,5 +1338,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface + * @return void + */ +- public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) ++ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider): void + { + $this->expressionLanguageProviders[] = $provider; +diff --git a/src/Symfony/Component/DependencyInjection/ContainerInterface.php b/src/Symfony/Component/DependencyInjection/ContainerInterface.php +index d2f4c343a1..92771c9628 100644 +--- a/src/Symfony/Component/DependencyInjection/ContainerInterface.php ++++ b/src/Symfony/Component/DependencyInjection/ContainerInterface.php +@@ -34,5 +34,5 @@ interface ContainerInterface extends PsrContainerInterface + * @return void + */ +- public function set(string $id, ?object $service); ++ public function set(string $id, ?object $service): void; + + /** +@@ -56,5 +56,5 @@ interface ContainerInterface extends PsrContainerInterface + * @throws ParameterNotFoundException if the parameter is not defined + */ +- public function getParameter(string $name); ++ public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null; + + public function hasParameter(string $name): bool; +@@ -63,4 +63,4 @@ interface ContainerInterface extends PsrContainerInterface + * @return void + */ +- public function setParameter(string $name, array|bool|string|int|float|\UnitEnum|null $value); ++ public function setParameter(string $name, array|bool|string|int|float|\UnitEnum|null $value): void; + } +diff --git a/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php b/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php +index 5f22fa53b6..2ebf0e385d 100644 +--- a/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php ++++ b/src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php +@@ -71,5 +71,5 @@ class AutowiringFailedException extends RuntimeException + * @return string + */ +- public function getServiceId() ++ public function getServiceId(): string + { + return $this->serviceId; +diff --git a/src/Symfony/Component/DependencyInjection/Exception/ParameterCircularReferenceException.php b/src/Symfony/Component/DependencyInjection/Exception/ParameterCircularReferenceException.php +index 9fc3b50b62..a6c1469c2d 100644 +--- a/src/Symfony/Component/DependencyInjection/Exception/ParameterCircularReferenceException.php ++++ b/src/Symfony/Component/DependencyInjection/Exception/ParameterCircularReferenceException.php +@@ -31,5 +31,5 @@ class ParameterCircularReferenceException extends RuntimeException + * @return array + */ +- public function getParameters() ++ public function getParameters(): array + { + return $this->parameters; +diff --git a/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php b/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php +index 69f7b3a50c..654537df61 100644 +--- a/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php ++++ b/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php +@@ -51,5 +51,5 @@ class ParameterNotFoundException extends InvalidArgumentException implements Not + * @return void + */ +- public function updateRepr() ++ public function updateRepr(): void + { + if (null !== $this->sourceId) { +@@ -78,5 +78,5 @@ class ParameterNotFoundException extends InvalidArgumentException implements Not + * @return string + */ +- public function getKey() ++ public function getKey(): string + { + return $this->key; +@@ -86,5 +86,5 @@ class ParameterNotFoundException extends InvalidArgumentException implements Not + * @return string|null + */ +- public function getSourceId() ++ public function getSourceId(): ?string + { + return $this->sourceId; +@@ -94,5 +94,5 @@ class ParameterNotFoundException extends InvalidArgumentException implements Not + * @return string|null + */ +- public function getSourceKey() ++ public function getSourceKey(): ?string + { + return $this->sourceKey; +@@ -102,5 +102,5 @@ class ParameterNotFoundException extends InvalidArgumentException implements Not + * @return void + */ +- public function setSourceId(?string $sourceId) ++ public function setSourceId(?string $sourceId): void + { + $this->sourceId = $sourceId; +@@ -112,5 +112,5 @@ class ParameterNotFoundException extends InvalidArgumentException implements Not + * @return void + */ +- public function setSourceKey(?string $sourceKey) ++ public function setSourceKey(?string $sourceKey): void + { + $this->sourceKey = $sourceKey; +diff --git a/src/Symfony/Component/DependencyInjection/Exception/ServiceCircularReferenceException.php b/src/Symfony/Component/DependencyInjection/Exception/ServiceCircularReferenceException.php +index d62c22567b..085d5fa167 100644 +--- a/src/Symfony/Component/DependencyInjection/Exception/ServiceCircularReferenceException.php ++++ b/src/Symfony/Component/DependencyInjection/Exception/ServiceCircularReferenceException.php +@@ -33,5 +33,5 @@ class ServiceCircularReferenceException extends RuntimeException + * @return string + */ +- public function getServiceId() ++ public function getServiceId(): string + { + return $this->serviceId; +@@ -41,5 +41,5 @@ class ServiceCircularReferenceException extends RuntimeException + * @return array + */ +- public function getPath() ++ public function getPath(): array + { + return $this->path; +diff --git a/src/Symfony/Component/DependencyInjection/Exception/ServiceNotFoundException.php b/src/Symfony/Component/DependencyInjection/Exception/ServiceNotFoundException.php +index d56db7727f..90da421299 100644 +--- a/src/Symfony/Component/DependencyInjection/Exception/ServiceNotFoundException.php ++++ b/src/Symfony/Component/DependencyInjection/Exception/ServiceNotFoundException.php +@@ -54,5 +54,5 @@ class ServiceNotFoundException extends InvalidArgumentException implements NotFo + * @return string + */ +- public function getId() ++ public function getId(): string + { + return $this->id; +@@ -62,5 +62,5 @@ class ServiceNotFoundException extends InvalidArgumentException implements NotFo + * @return string|null + */ +- public function getSourceId() ++ public function getSourceId(): ?string + { + return $this->sourceId; +@@ -70,5 +70,5 @@ class ServiceNotFoundException extends InvalidArgumentException implements NotFo + * @return array + */ +- public function getAlternatives() ++ public function getAlternatives(): array + { + return $this->alternatives; +diff --git a/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php b/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php +index a42967f4da..4e86e16f9d 100644 +--- a/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php ++++ b/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php +@@ -27,4 +27,4 @@ interface ConfigurationExtensionInterface + * @return ConfigurationInterface|null + */ +- public function getConfiguration(array $config, ContainerBuilder $container); ++ public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface; + } +diff --git a/src/Symfony/Component/DependencyInjection/Extension/Extension.php b/src/Symfony/Component/DependencyInjection/Extension/Extension.php +index d0bd05ea4b..f9df65fd7c 100644 +--- a/src/Symfony/Component/DependencyInjection/Extension/Extension.php ++++ b/src/Symfony/Component/DependencyInjection/Extension/Extension.php +@@ -32,5 +32,5 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn + * @return string|false + */ +- public function getXsdValidationBasePath() ++ public function getXsdValidationBasePath(): string|false + { + return false; +@@ -40,5 +40,5 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn + * @return string + */ +- public function getNamespace() ++ public function getNamespace(): string + { + return 'http://example.org/schema/dic/'.$this->getAlias(); +@@ -77,5 +77,5 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn + * @return ConfigurationInterface|null + */ +- public function getConfiguration(array $config, ContainerBuilder $container) ++ public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface + { + $class = static::class; +diff --git a/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php b/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php +index bd57eef733..3284e19ede 100644 +--- a/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php ++++ b/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php +@@ -30,5 +30,5 @@ interface ExtensionInterface + * @throws \InvalidArgumentException When provided tag is not defined in this extension + */ +- public function load(array $configs, ContainerBuilder $container); ++ public function load(array $configs, ContainerBuilder $container): void; + + /** +@@ -37,5 +37,5 @@ interface ExtensionInterface + * @return string + */ +- public function getNamespace(); ++ public function getNamespace(): string; + + /** +@@ -44,5 +44,5 @@ interface ExtensionInterface + * @return string|false + */ +- public function getXsdValidationBasePath(); ++ public function getXsdValidationBasePath(): string|false; + + /** +@@ -53,4 +53,4 @@ interface ExtensionInterface + * @return string + */ +- public function getAlias(); ++ public function getAlias(): string; + } +diff --git a/src/Symfony/Component/DependencyInjection/Extension/PrependExtensionInterface.php b/src/Symfony/Component/DependencyInjection/Extension/PrependExtensionInterface.php +index 0df94e1092..061e7d7fd9 100644 +--- a/src/Symfony/Component/DependencyInjection/Extension/PrependExtensionInterface.php ++++ b/src/Symfony/Component/DependencyInjection/Extension/PrependExtensionInterface.php +@@ -21,4 +21,4 @@ interface PrependExtensionInterface + * @return void + */ +- public function prepend(ContainerBuilder $container); ++ public function prepend(ContainerBuilder $container): void; + } +diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php +index f4c6b29258..1402331f9e 100644 +--- a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php ++++ b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php +@@ -31,4 +31,4 @@ interface InstantiatorInterface + * @return object + */ +- public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator); ++ public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object; + } +diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +index c817f16422..0605b0a773 100644 +--- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php ++++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +@@ -99,5 +99,5 @@ abstract class FileLoader extends BaseFileLoader + * @return void + */ +- public function registerClasses(Definition $prototype, string $namespace, string $resource, string|array $exclude = null/* , string $source = null */) ++ public function registerClasses(Definition $prototype, string $namespace, string $resource, string|array $exclude = null/* , string $source = null */): void + { + if (!str_ends_with($namespace, '\\')) { +@@ -195,5 +195,5 @@ abstract class FileLoader extends BaseFileLoader + * @return void + */ +- public function registerAliasesForSinglyImplementedInterfaces() ++ public function registerAliasesForSinglyImplementedInterfaces(): void + { + foreach ($this->interfaces as $interface) { +@@ -211,5 +211,5 @@ abstract class FileLoader extends BaseFileLoader + * @return void + */ +- protected function setDefinition(string $id, Definition $definition) ++ protected function setDefinition(string $id, Definition $definition): void + { + $this->container->removeBindings($id); +diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/ContainerBagInterface.php b/src/Symfony/Component/DependencyInjection/ParameterBag/ContainerBagInterface.php +index eeff6538c5..8ac7149b37 100644 +--- a/src/Symfony/Component/DependencyInjection/ParameterBag/ContainerBagInterface.php ++++ b/src/Symfony/Component/DependencyInjection/ParameterBag/ContainerBagInterface.php +@@ -40,5 +40,5 @@ interface ContainerBagInterface extends ContainerInterface + * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist + */ +- public function resolveValue(mixed $value); ++ public function resolveValue(mixed $value): mixed; + + /** +diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php b/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php +index 9c66e1f944..619e44fc73 100644 +--- a/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php ++++ b/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php +@@ -91,5 +91,5 @@ class EnvPlaceholderParameterBag extends ParameterBag + * @return void + */ +- public function clearUnusedEnvPlaceholders() ++ public function clearUnusedEnvPlaceholders(): void + { + $this->unusedEnvPlaceholders = []; +@@ -101,5 +101,5 @@ class EnvPlaceholderParameterBag extends ParameterBag + * @return void + */ +- public function mergeEnvPlaceholders(self $bag) ++ public function mergeEnvPlaceholders(self $bag): void + { + if ($newPlaceholders = $bag->getEnvPlaceholders()) { +@@ -125,5 +125,5 @@ class EnvPlaceholderParameterBag extends ParameterBag + * @return void + */ +- public function setProvidedTypes(array $providedTypes) ++ public function setProvidedTypes(array $providedTypes): void + { + $this->providedTypes = $providedTypes; +@@ -143,5 +143,5 @@ class EnvPlaceholderParameterBag extends ParameterBag + * @return void + */ +- public function resolve() ++ public function resolve(): void + { + if ($this->resolved) { +diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php b/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php +index 1ede090384..7b6b63c599 100644 +--- a/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php ++++ b/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php +@@ -38,5 +38,5 @@ class FrozenParameterBag extends ParameterBag + * @return never + */ +- public function clear() ++ public function clear(): never + { + throw new LogicException('Impossible to call clear() on a frozen ParameterBag.'); +@@ -46,5 +46,5 @@ class FrozenParameterBag extends ParameterBag + * @return never + */ +- public function add(array $parameters) ++ public function add(array $parameters): never + { + throw new LogicException('Impossible to call add() on a frozen ParameterBag.'); +@@ -54,5 +54,5 @@ class FrozenParameterBag extends ParameterBag + * @return never + */ +- public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value) ++ public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value): never + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); +@@ -62,5 +62,5 @@ class FrozenParameterBag extends ParameterBag + * @return never + */ +- public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.') ++ public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.'): never + { + throw new LogicException('Impossible to call deprecate() on a frozen ParameterBag.'); +@@ -70,5 +70,5 @@ class FrozenParameterBag extends ParameterBag + * @return never + */ +- public function remove(string $name) ++ public function remove(string $name): never + { + throw new LogicException('Impossible to call remove() on a frozen ParameterBag.'); +diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php b/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php +index 6ba8a4cf7c..5e5e22bc09 100644 +--- a/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php ++++ b/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php +@@ -35,5 +35,5 @@ class ParameterBag implements ParameterBagInterface + * @return void + */ +- public function clear() ++ public function clear(): void + { + $this->parameters = []; +@@ -43,5 +43,5 @@ class ParameterBag implements ParameterBagInterface + * @return void + */ +- public function add(array $parameters) ++ public function add(array $parameters): void + { + foreach ($parameters as $key => $value) { +@@ -104,5 +104,5 @@ class ParameterBag implements ParameterBagInterface + * @return void + */ +- public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value) ++ public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value): void + { + if (is_numeric($name)) { +@@ -122,5 +122,5 @@ class ParameterBag implements ParameterBagInterface + * @throws ParameterNotFoundException if the parameter is not defined + */ +- public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.') ++ public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.'): void + { + if (!\array_key_exists($name, $this->parameters)) { +@@ -139,5 +139,5 @@ class ParameterBag implements ParameterBagInterface + * @return void + */ +- public function remove(string $name) ++ public function remove(string $name): void + { + unset($this->parameters[$name], $this->deprecatedParameters[$name]); +@@ -147,5 +147,5 @@ class ParameterBag implements ParameterBagInterface + * @return void + */ +- public function resolve() ++ public function resolve(): void + { + if ($this->resolved) { +@@ -259,5 +259,5 @@ class ParameterBag implements ParameterBagInterface + * @return bool + */ +- public function isResolved() ++ public function isResolved(): bool + { + return $this->resolved; +diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBagInterface.php b/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBagInterface.php +index 18ddfde147..b8651648bd 100644 +--- a/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBagInterface.php ++++ b/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBagInterface.php +@@ -29,5 +29,5 @@ interface ParameterBagInterface + * @throws LogicException if the ParameterBagInterface cannot be cleared + */ +- public function clear(); ++ public function clear(): void; + + /** +@@ -38,5 +38,5 @@ interface ParameterBagInterface + * @throws LogicException if the parameter cannot be added + */ +- public function add(array $parameters); ++ public function add(array $parameters): void; + + /** +@@ -57,5 +57,5 @@ interface ParameterBagInterface + * @return void + */ +- public function remove(string $name); ++ public function remove(string $name): void; + + /** +@@ -66,5 +66,5 @@ interface ParameterBagInterface + * @throws LogicException if the parameter cannot be set + */ +- public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value); ++ public function set(string $name, array|bool|string|int|float|\UnitEnum|null $value): void; + + /** +@@ -78,5 +78,5 @@ interface ParameterBagInterface + * @return void + */ +- public function resolve(); ++ public function resolve(): void; + + /** +@@ -87,5 +87,5 @@ interface ParameterBagInterface + * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist + */ +- public function resolveValue(mixed $value); ++ public function resolveValue(mixed $value): mixed; + + /** +diff --git a/src/Symfony/Component/DependencyInjection/TypedReference.php b/src/Symfony/Component/DependencyInjection/TypedReference.php +index 9b431cd65b..5fdb0643cd 100644 +--- a/src/Symfony/Component/DependencyInjection/TypedReference.php ++++ b/src/Symfony/Component/DependencyInjection/TypedReference.php +@@ -41,5 +41,5 @@ class TypedReference extends Reference + * @return string + */ +- public function getType() ++ public function getType(): string + { + return $this->type; +diff --git a/src/Symfony/Component/DomCrawler/AbstractUriElement.php b/src/Symfony/Component/DomCrawler/AbstractUriElement.php +index f610b014a0..9458751c28 100644 +--- a/src/Symfony/Component/DomCrawler/AbstractUriElement.php ++++ b/src/Symfony/Component/DomCrawler/AbstractUriElement.php +@@ -120,4 +120,4 @@ abstract class AbstractUriElement + * @throws \LogicException If given node is not an anchor + */ +- abstract protected function setNode(\DOMElement $node); ++ abstract protected function setNode(\DOMElement $node): void; + } +diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php +index 59eec3068c..b750e80938 100644 +--- a/src/Symfony/Component/DomCrawler/Crawler.php ++++ b/src/Symfony/Component/DomCrawler/Crawler.php +@@ -96,5 +96,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @return void + */ +- public function clear() ++ public function clear(): void + { + $this->nodes = []; +@@ -115,5 +115,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @throws \InvalidArgumentException when node is not the expected type + */ +- public function add(\DOMNodeList|\DOMNode|array|string|null $node) ++ public function add(\DOMNodeList|\DOMNode|array|string|null $node): void + { + if ($node instanceof \DOMNodeList) { +@@ -139,5 +139,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @return void + */ +- public function a 8000 ddContent(string $content, string $type = null) ++ public function addContent(string $content, string $type = null): void + { + if (empty($type)) { +@@ -181,5 +181,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @return void + */ +- public function addHtmlContent(string $content, string $charset = 'UTF-8') ++ public function addHtmlContent(string $content, string $charset = 'UTF-8'): void + { + $dom = $this->parseHtmlString($content, $charset); +@@ -217,5 +217,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @return void + */ +- public function addXmlContent(string $content, string $charset = 'UTF-8', int $options = \LIBXML_NONET) ++ public function addXmlContent(string $content, string $charset = 'UTF-8', int $options = \LIBXML_NONET): void + { + // remove the default namespace if it's the only namespace to make XPath expressions simpler +@@ -247,5 +247,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @return void + */ +- public function addDocument(\DOMDocument $dom) ++ public function addDocument(\DOMDocument $dom): void + { + if ($dom->documentElement) { +@@ -261,5 +261,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @return void + */ +- public function addNodeList(\DOMNodeList $nodes) ++ public function addNodeList(\DOMNodeList $nodes): void + { + foreach ($nodes as $node) { +@@ -277,5 +277,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @return void + */ +- public function addNodes(array $nodes) ++ public function addNodes(array $nodes): void + { + foreach ($nodes as $node) { +@@ -291,5 +291,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @return void + */ +- public function addNode(\DOMNode $node) ++ public function addNode(\DOMNode $node): void + { + if ($node instanceof \DOMDocument) { +@@ -885,5 +885,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @return void + */ +- public function setDefaultNamespacePrefix(string $prefix) ++ public function setDefaultNamespacePrefix(string $prefix): void + { + $this->defaultNamespacePrefix = $prefix; +@@ -893,5 +893,5 @@ class Crawler implements \Countable, \IteratorAggregate + * @return void + */ +- public function registerNamespace(string $prefix, string $namespace) ++ public function registerNamespace(string $prefix, string $namespace): void + { + $this->namespaces[$prefix] = $namespace; +diff --git a/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php b/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php +index dcae5490ad..4357de8275 100644 +--- a/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php ++++ b/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php +@@ -64,5 +64,5 @@ class ChoiceFormField extends FormField + * @return void + */ +- public function select(string|array|bool $value) ++ public function select(string|array|bool $value): void + { + $this->setValue($value); +@@ -76,5 +76,5 @@ class ChoiceFormField extends FormField + * @throws \LogicException When the type provided is not correct + */ +- public function tick() ++ public function tick(): void + { + if ('checkbox' !== $this->type) { +@@ -92,5 +92,5 @@ class ChoiceFormField extends FormField + * @throws \LogicException When the type provided is not correct + */ +- public function untick() ++ public function untick(): void + { + if ('checkbox' !== $this->type) { +@@ -108,5 +108,5 @@ class ChoiceFormField extends FormField + * @throws \InvalidArgumentException When value type provided is not correct + */ +- public function setValue(string|array|bool|null $value) ++ public function setValue(string|array|bool|null $value): void + { + if ('checkbox' === $this->type && false === $value) { +@@ -187,5 +187,5 @@ class ChoiceFormField extends FormField + * @throws \LogicException When node type is incorrect + */ +- protected function initialize() ++ protected function initialize(): void + { + if ('input' !== $this->node->nodeName && 'select' !== $this->node->nodeName) { +diff --git a/src/Symfony/Component/DomCrawler/Field/FileFormField.php b/src/Symfony/Component/DomCrawler/Field/FileFormField.php +index 4ebe766f0b..eca59a131d 100644 +--- a/src/Symfony/Component/DomCrawler/Field/FileFormField.php ++++ b/src/Symfony/Component/DomCrawler/Field/FileFormField.php +@@ -28,5 +28,5 @@ class FileFormField extends FormField + * @throws \InvalidArgumentException When error code doesn't exist + */ +- public function setErrorCode(int $error) ++ public function setErrorCode(int $error): void + { + $codes = [\UPLOAD_ERR_INI_SIZE, \UPLOAD_ERR_FORM_SIZE, \UPLOAD_ERR_PARTIAL, \UPLOAD_ERR_NO_FILE, \UPLOAD_ERR_NO_TMP_DIR, \UPLOAD_ERR_CANT_WRITE, \UPLOAD_ERR_EXTENSION]; +@@ -43,5 +43,5 @@ class FileFormField extends FormField + * @return void + */ +- public function upload(?string $value) ++ public function upload(?string $value): void + { + $this->setValue($value); +@@ -53,5 +53,5 @@ class FileFormField extends FormField + * @return void + */ +- public function setValue(?string $value) ++ public function setValue(?string $value): void + { + if (null !== $value && is_readable($value)) { +@@ -86,5 +86,5 @@ class FileFormField extends FormField + * @return void + */ +- public function setFilePath(string $path) ++ public function setFilePath(string $path): void + { + parent::setValue($path); +@@ -98,5 +98,5 @@ class FileFormField extends FormField + * @throws \LogicException When node type is incorrect + */ +- protected function initialize() ++ protected function initialize(): void + { + if ('input' !== $this->node->nodeName) { +diff --git a/src/Symfony/Component/DomCrawler/Field/FormField.php b/src/Symfony/Component/DomCrawler/Field/FormField.php +index b97d54dda0..b6a9ebb549 100644 +--- a/src/Symfony/Component/DomCrawler/Field/FormField.php ++++ b/src/Symfony/Component/DomCrawler/Field/FormField.php +@@ -96,5 +96,5 @@ abstract class FormField + * @return void + */ +- public function setValue(?string $value) ++ public function setValue(?string $value): void + { + $this->value = $value ?? ''; +@@ -122,4 +122,4 @@ abstract class FormField + * @return void + */ +- abstract protected function initialize(); ++ abstract protected function initialize(): void; + } +diff --git a/src/Symfony/Component/DomCrawler/Field/InputFormField.php b/src/Symfony/Component/DomCrawler/Field/InputFormField.php +index 19d77352fc..ece901b578 100644 +--- a/src/Symfony/Component/DomCrawler/Field/InputFormField.php ++++ b/src/Symfony/Component/DomCrawler/Field/InputFormField.php +@@ -29,5 +29,5 @@ class InputFormField extends FormField + * @throws \LogicException When node type is incorrect + */ +- protected function initialize() ++ protected function initialize(): void + { + if ('input' !== $this->node->nodeName && 'button' !== $this->node->nodeName) { +diff --git a/src/Symfony/Component/DomCrawler/Field/TextareaFormField.php b/src/Symfony/Component/DomCrawler/Field/TextareaFormField.php +index 5168c52251..cf22473776 100644 +--- a/src/Symfony/Component/DomCrawler/Field/TextareaFormField.php ++++ b/src/Symfony/Component/DomCrawler/Field/TextareaFormField.php +@@ -26,5 +26,5 @@ class TextareaFormField extends FormField + * @throws \LogicException When node type is incorrect + */ +- protected function initialize() ++ protected function initialize(): void + { + if ('textarea' !== $this->node->nodeName) { +diff --git a/src/Symfony/Component/DomCrawler/Form.php b/src/Symfony/Component/DomCrawler/Form.php +index 9e53bbb680..51477a8ed7 100644 +--- a/src/Symfony/Component/DomCrawler/Form.php ++++ b/src/Symfony/Component/DomCrawler/Form.php +@@ -249,5 +249,5 @@ class Form extends Link implements \ArrayAccess + * @return void + */ +- public function remove(string $name) ++ public function remove(string $name): void + { + $this->fields->remove($name); +@@ -271,5 +271,5 @@ class Form extends Link implements \ArrayAccess + * @return void + */ +- public function set(FormField $field) ++ public function set(FormField $field): void + { + $this->fields->add($field); +@@ -358,5 +358,5 @@ class Form extends Link implements \ArrayAccess + * @throws \LogicException If given node is not a button or input or does not have a form ancestor + */ +- protected function setNode(\DOMElement $node) ++ protected function setNode(\DOMElement $node): void + { + $this->button = $node; +diff --git a/src/Symfony/Component/DomCrawler/Image.php b/src/Symfony/Component/DomCrawler/Image.php +index 725e3aea38..9ada91a4be 100644 +--- a/src/Symfony/Component/DomCrawler/Image.php ++++ b/src/Symfony/Component/DomCrawler/Image.php +@@ -30,5 +30,5 @@ class Image extends AbstractUriElement + * @return void + */ +- protected function setNode(\DOMElement $node) ++ protected function setNode(\DOMElement $node): void + { + if ('img' !== $node->nodeName) { +diff --git a/src/Symfony/Component/DomCrawler/Link.php b/src/Symfony/Component/DomCrawler/Link.php +index 681a2f7a23..07ca3531a1 100644 +--- a/src/Symfony/Component/DomCrawler/Link.php ++++ b/src/Symfony/Component/DomCrawler/Link.php +@@ -27,5 +27,5 @@ class Link extends AbstractUriElement + * @return void + */ +- protected function setNode(\DOMElement $node) ++ protected function setNode(\DOMElement $node): void + { + if ('a' !== $node->nodeName && 'area' !== $node->nodeName && 'link' !== $node->nodeName) { +diff --git a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php +index f1b982315c..ed8ad1fab4 100644 +--- a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php ++++ b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php +@@ -55,5 +55,5 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa + * @return void + */ +- public function addListener(string $eventName, callable|array $listener, int $priority = 0) ++ public function addListener(string $eventName, callable|array $listener, int $priority = 0): void + { + $this->dispatcher->addListener($eventName, $listener, $priority); +@@ -63,5 +63,5 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa + * @return void + */ +- public function addSubscriber(EventSubscriberInterface $subscriber) ++ public function addSubscriber(EventSubscriberInterface $subscriber): void + { + $this->dispatcher->addSubscriber($subscriber); +@@ -71,5 +71,5 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa + * @return void + */ +- public function removeListener(string $eventName, callable|array $listener) ++ public function removeListener(string $eventName, callable|array $listener): void + { + if (isset($this->wrappedListeners[$eventName])) { +@@ -89,5 +89,5 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa + * @return void + */ +- public function removeSubscriber(EventSubscriberInterface $subscriber) ++ public function removeSubscriber(EventSubscriberInterface $subscriber): void + { + $this->dispatcher->removeSubscriber($subscriber); +@@ -230,5 +230,5 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa + * @return void + */ +- public function reset() ++ public function reset(): void + { + $this->callStack = null; +@@ -253,5 +253,5 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa + * @return void + */ +- protected function beforeDispatch(string $eventName, object $event) ++ protected function beforeDispatch(string $eventName, object $event): void + { + } +@@ -262,5 +262,5 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa + * @return void + */ +- protected function afterDispatch(string $eventName, object $event) ++ protected function afterDispatch(string $eventName, object $event): void + { + } +diff --git a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php +index c86f438d41..3bfb39db57 100644 +--- a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php ++++ b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php +@@ -52,5 +52,5 @@ class RegisterListenersPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('event_dispatcher') && !$container->hasAlias('event_dispatcher')) { +diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcher.php b/src/Symfony/Component/EventDispatcher/EventDispatcher.php +index 327803af67..2466d748ec 100644 +--- a/src/Symfony/Component/EventDispatcher/EventDispatcher.php ++++ b/src/Symfony/Component/EventDispatcher/EventDispatcher.php +@@ -127,5 +127,5 @@ class EventDispatcher implements EventDispatcherInterface + * @return void + */ +- public function addListener(string $eventName, callable|array $listener, int $priority = 0) ++ public function addListener(string $eventName, callable|array $listener, int $priority = 0): void + { + $this->listeners[$eventName][$priority][] = $listener; +@@ -136,5 +136,5 @@ class EventDispatcher implements EventDispatcherInterface + * @return void + */ +- public function removeListener(string $eventName, callable|array $listener) ++ public function removeListener(string $eventName, callable|array $listener): void + { + if (empty($this->listeners[$eventName])) { +@@ -167,5 +167,5 @@ class EventDispatcher implements EventDispatcherInterface + * @return void + */ +- public function addSubscriber(EventSubscriberInterface $subscriber) ++ public function addSubscriber(EventSubscriberInterface $subscriber): void + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { +@@ -185,5 +185,5 @@ class EventDispatcher implements EventDispatcherInterface + * @return void + */ +- public function removeSubscriber(EventSubscriberInterface $subscriber) ++ public function removeSubscriber(EventSubscriberInterface $subscriber): void + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { +@@ -210,5 +210,5 @@ class EventDispatcher implements EventDispatcherInterface + * @return void + */ +- protected function callListeners(iterable $listeners, string $eventName, object $event) ++ protected function callListeners(iterable $listeners, string $eventName, object $event): void + { + $stoppable = $event instanceof StoppableEventInterface; +diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php b/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php +index 3cd94c9388..c423905c11 100644 +--- a/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php ++++ b/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php +@@ -31,5 +31,5 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface + * @return void + */ +- public function addListener(string $eventName, callable $listener, int $priority = 0); ++ public function addListener(string $eventName, callable $listener, int $priority = 0): void; + + /** +@@ -41,5 +41,5 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface + * @return void + */ +- public function addSubscriber(EventSubscriberInterface $subscriber); ++ public function addSubscriber(EventSubscriberInterface $subscriber): void; + + /** +@@ -48,10 +48,10 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface + * @return void + */ +- public function removeListener(string $eventName, callable $listener); ++ public function removeListener(string $eventName, callable $listener): void; + + /** + * @return void + */ +- public function removeSubscriber(EventSubscriberInterface $subscriber); ++ public function removeSubscriber(EventSubscriberInterface $subscriber): void; + + /** +diff --git a/src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php b/src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php +index 2085e428e9..ca0d6964e5 100644 +--- a/src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php ++++ b/src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php +@@ -46,4 +46,4 @@ interface EventSubscriberInterface + * @return array> + */ +- public static function getSubscribedEvents(); ++ public static function getSubscribedEvents(): array; + } +diff --git a/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php +index d385d3f833..1fc9f23ea0 100644 +--- a/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php ++++ b/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php +@@ -34,5 +34,5 @@ class ImmutableEventDispatcher implements EventDispatcherInterface + * @return never + */ +- public function addListener(string $eventName, callable|array $listener, int $priority = 0) ++ public function addListener(string $eventName, callable|array $listener, int $priority = 0): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); +@@ -42,5 +42,5 @@ class ImmutableEventDispatcher implements EventDispatcherInterface + * @return never + */ +- public function addSubscriber(EventSubscriberInterface $subscriber) ++ public function addSubscriber(EventSubscriberInterface $subscriber): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); +@@ -50,5 +50,5 @@ class ImmutableEventDispatcher implements EventDispatcherInterface + * @return never + */ +- public function removeListener(string $eventName, callable|array $listener) ++ public function removeListener(string $eventName, callable|array $listener): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); +@@ -58,5 +58,5 @@ class ImmutableEventDispatcher implements EventDispatcherInterface + * @return never + */ +- public function removeSubscriber(EventSubscriberInterface $subscriber) ++ public function removeSubscriber(EventSubscriberInterface $subscriber): never + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); +diff --git a/src/Symfony/Component/ExpressionLanguage/Compiler.php b/src/Symfony/Component/ExpressionLanguage/Compiler.php +index ab50d361e3..c25e18117b 100644 +--- a/src/Symfony/Component/ExpressionLanguage/Compiler.php ++++ b/src/Symfony/Component/ExpressionLanguage/Compiler.php +@@ -32,5 +32,5 @@ class Compiler implements ResetInterface + * @return array + */ +- public function getFunction(string $name) ++ public function getFunction(string $name): array + { + return $this->functions[$name]; +@@ -70,5 +70,5 @@ class Compiler implements ResetInterface + * @return string + */ +- public function subcompile(Node\Node $node) ++ public function subcompile(Node\Node $node): string + { + $current = $this->source; +diff --git a/src/Symfony/Component/ExpressionLanguage/ExpressionFunctionProviderInterface.php b/src/Symfony/Component/ExpressionLanguage/ExpressionFunctionProviderInterface.php +index 479aeef880..272954c082 100644 +--- a/src/Symfony/Component/ExpressionLanguage/ExpressionFunctionProviderInterface.php ++++ b/src/Symfony/Component/ExpressionLanguage/ExpressionFunctionProviderInterface.php +@@ -20,4 +20,4 @@ interface ExpressionFunctionProviderInterface + * @return ExpressionFunction[] + */ +- public function getFunctions(); ++ public function getFunctions(): array; + } +diff --git a/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php b/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php +index 9e107401a2..7f92321a2b 100644 +--- a/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php ++++ b/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php +@@ -117,5 +117,5 @@ class ExpressionLanguage + * @see ExpressionFunction + */ +- public function register(string $name, callable $compiler, callable $evaluator) ++ public function register(string $name, callable $compiler, callable $evaluator): void + { + if (isset($this->parser)) { +@@ -129,5 +129,5 @@ class ExpressionLanguage + * @return void + */ +- public function addFunction(ExpressionFunction $function) ++ public function addFunction(ExpressionFunction $function): void + { + $this->register($function->getName(), $function->getCompiler(), $function->getEvaluator()); +@@ -137,5 +137,5 @@ class ExpressionLanguage + * @return void + */ +- public function registerProvider(ExpressionFunctionProviderInterface $provider) ++ public function registerProvider(ExpressionFunctionProviderInterface $provider): void + { + foreach ($provider->getFunctions() as $function) { +@@ -147,5 +147,5 @@ class ExpressionLanguage + * @return void + */ +- protected function registerFunctions() ++ protected function registerFunctions(): void + { + $this->addFunction(ExpressionFunction::fromPhp('constant')); +diff --git a/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php b/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php +index 33323f388f..811fec7e2e 100644 +--- a/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php ++++ b/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php +@@ -54,5 +54,5 @@ class FunctionNode extends Node + * @return array + */ +- public function toArray() ++ public function toArray(): array + { + $array = []; +diff --git a/src/Symfony/Component/ExpressionLanguage/Node/Node.php b/src/Symfony/Component/ExpressionLanguage/Node/Node.php +index 91fcc363ed..8756971315 100644 +--- a/src/Symfony/Component/ExpressionLanguage/Node/Node.php ++++ b/src/Symfony/Component/ExpressionLanguage/Node/Node.php +@@ -61,5 +61,5 @@ class Node + * @return void + */ +- public function compile(Compiler $compiler) ++ public function compile(Compiler $compiler): void + { + foreach ($this->nodes as $node) { +@@ -71,5 +71,5 @@ class Node + * @return mixed + */ +- public function evaluate(array $functions, array $values) ++ public function evaluate(array $functions, array $values): mixed + { + $results = []; +@@ -86,5 +86,5 @@ class Node + * @throws \BadMethodCallException when this node cannot be transformed to an array + */ +- public function toArray() ++ public function toArray(): array + { + throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', static::class)); +@@ -94,5 +94,5 @@ class Node + * @return string + */ +- public function dump() ++ public function dump(): string + { + $dump = ''; +@@ -108,5 +108,5 @@ class Node + * @return string + */ +- protected function dumpString(string $value) ++ protected function dumpString(string $value): string + { + return sprintf('"%s"', addcslashes($value, "\0\t\"\\")); +@@ -116,5 +116,5 @@ class Node + * @return bool + */ +- protected function isHash(array $value) ++ protected function isHash(array $value): bool + { + $expectedKey = 0; +diff --git a/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php b/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php +index 239624ec2c..3b497d5ccf 100644 +--- a/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php ++++ b/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php +@@ -33,5 +33,5 @@ class ParsedExpression extends Expression + * @return Node + */ +- public function getNodes() ++ public function getNodes(): Node + { + return $this->nodes; +diff --git a/src/Symfony/Component/ExpressionLanguage/Parser.php b/src/Symfony/Component/ExpressionLanguage/Parser.php +index a163a7a82f..3f83e0cfe8 100644 +--- a/src/Symfony/Component/ExpressionLanguage/Parser.php ++++ b/src/Symfony/Component/ExpressionLanguage/Parser.php +@@ -134,5 +134,5 @@ class Parser + * @return Node\Node + */ +- public function parseExpression(int $precedence = 0) ++ public function parseExpression(int $precedence = 0): Node\Node + { + $expr = $this->getPrimary(); +@@ -158,5 +158,5 @@ class Parser + * @return Node\Node + */ +- protected function getPrimary() ++ protected function getPrimary(): Node\Node + { + $token = $this->stream->current; +@@ -184,5 +184,5 @@ class Parser + * @return Node\Node + */ +- protected function parseConditionalExpression(Node\Node $expr) ++ protected function parseConditionalExpression(Node\Node $expr): Node\Node + { + while ($this->stream->current->test(Token::PUNCTUATION_TYPE, '??')) { +@@ -218,5 +218,5 @@ class Parser + * @return Node\Node + */ +- public function parsePrimaryExpression() ++ public function parsePrimaryExpression(): Node\Node + { + $token = $this->stream->current; +@@ -286,5 +286,5 @@ class Parser + * @return Node\ArrayNode + */ +- public function parseArrayExpression() ++ public function parseArrayExpression(): Node\ArrayNode + { + $this->stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); +@@ -313,5 +313,5 @@ class Parser + * @return Node\ArrayNode + */ +- public function parseHashExpression() ++ public function parseHashExpression(): Node\ArrayNode + { + $this->stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); +@@ -360,5 +360,5 @@ class Parser + * @return Node\GetAttrNode|Node\Node + */ +- public function parsePostfixExpression(Node\Node $node) ++ public function parsePostfixExpression(Node\Node $node): Node\GetAttrNode|Node\Node + { + $token = $this->stream->current; +@@ -422,5 +422,5 @@ class Parser + * @return Node\Node + */ +- public function parseArguments() ++ public function parseArguments(): Node\Node + { + $args = []; +diff --git a/src/Symfony/Component/ExpressionLanguage/SerializedParsedExpression.php b/src/Symfony/Component/ExpressionLanguage/SerializedParsedExpression.php +index 5691907c86..92d423af86 100644 +--- a/src/Symfony/Component/ExpressionLanguage/SerializedParsedExpression.php ++++ b/src/Symfony/Component/ExpressionLanguage/SerializedParsedExpression.php +@@ -36,5 +36,5 @@ class SerializedParsedExpression extends ParsedExpression + * @return Node + */ +- public function getNodes() ++ public function getNodes(): Node + { + return unserialize($this->nodes); +diff --git a/src/Symfony/Component/ExpressionLanguage/TokenStream.php b/src/Symfony/Component/ExpressionLanguage/TokenStream.php +index 241725b9c5..420932897f 100644 +--- a/src/Symfony/Component/ExpressionLanguage/TokenStream.php ++++ b/src/Symfony/Component/ExpressionLanguage/TokenStream.php +@@ -45,5 +45,5 @@ class TokenStream + * @return void + */ +- public function next() ++ public function next(): void + { + ++$this->position; +@@ -61,5 +61,5 @@ class TokenStream + * @return void + */ +- public function expect(string $type, string $value = null, string $message = null) ++ public function expect(string $type, string $value = null, string $message = null): void + { + $token = $this->current; +diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php +index d3280bee51..913c309358 100644 +--- a/src/Symfony/Component/Filesystem/Filesystem.php ++++ b/src/Symfony/Component/Filesystem/Filesystem.php +@@ -37,5 +37,5 @@ class Filesystem + * @throws IOException When copy fails + */ +- public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false) ++ public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false): void + { + $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://'); +@@ -89,5 +89,5 @@ class Filesystem + * @throws IOException On any directory creation failure + */ +- public function mkdir(string|iterable $dirs, int $mode = 0777) ++ public function mkdir(string|iterable $dirs, int $mode = 0777): void + { + foreach ($this->toIterable($dirs) as $dir) { +@@ -132,5 +132,5 @@ class Filesystem + * @throws IOException When touch fails + */ +- public function touch(string|iterable $files, int $time = null, int $atime = null) ++ public function touch(string|iterable $files, int $time = null, int $atime = null): void + { + foreach ($this->toIterable($files) as $file) { +@@ -148,5 +148,5 @@ class Filesystem + * @throws IOException When removal fails + */ +- public function remove(string|iterable $files) ++ public function remove(string|iterable $files): void + { + if ($files instanceof \Traversable) { +@@ -216,5 +216,5 @@ class Filesystem + * @throws IOException When the change fails + */ +- public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false) ++ public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false): void + { + foreach ($this->toIterable($files) as $file) { +@@ -238,5 +238,5 @@ class Filesystem + * @throws IOException When the change fails + */ +- public function chown(string|iterable $files, string|int $user, bool $recursive = false) ++ public function chown(string|iterable $files, string|int $user, bool $recursive = false): void + { + foreach ($this->toIterable($files) as $file) { +@@ -266,5 +266,5 @@ class Filesystem + * @throws IOException When the change fails + */ +- public function chgrp(string|iterable $files, string|int $group, bool $recursive = false) ++ public function chgrp(string|iterable $files, string|int $group, bool $recursive = false): void + { + foreach ($this->toIterable($files) as $file) { +@@ -292,5 +292,5 @@ class Filesystem + * @throws IOException When origin cannot be renamed + */ +- public function rename(string $origin, string $target, bool $overwrite = false) ++ public function rename(string $origin, string $target, bool $overwrite = false): void + { + // we check that target does not exist +@@ -334,5 +334,5 @@ class Filesystem + * @throws IOException When symlink fails + */ +- public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false) ++ public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false): void + { + self::assertFunctionExists('symlink'); +@@ -373,5 +373,5 @@ class Filesystem + * @throws IOException When link fails, including if link already exists + */ +- public function hardlink(string $originFile, string|iterable $targetFiles) ++ public function hardlink(string $originFile, string|iterable $targetFiles): void + { + self::assertFunctionExists('link'); +@@ -531,5 +531,5 @@ class Filesystem + * @throws IOException When file type is unknown + 10000 */ +- public function mirror(string $originDir, string $targetDir, \Traversable $iterator = null, array $options = []) ++ public function mirror(string $originDir, string $targetDir, \Traversable $iterator = null, array $options = []): void + { + $targetDir = rtrim($targetDir, '/\\'); +@@ -657,5 +657,5 @@ class Filesystem + * @throws IOException if the file cannot be written to + */ +- public function dumpFile(string $filename, $content) ++ public function dumpFile(string $filename, $content): void + { + if (\is_array($content)) { +@@ -698,5 +698,5 @@ class Filesystem + * @throws IOException If the file is not writable + */ +- public function appendToFile(string $filename, $content/* , bool $lock = false */) ++ public function appendToFile(string $filename, $content/* , bool $lock = false */): void + { + if (\is_array($content)) { +diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php +index 62c3f9e24f..f5055abd0c 100644 +--- a/src/Symfony/Component/Finder/Finder.php ++++ b/src/Symfony/Component/Finder/Finder.php +@@ -400,5 +400,5 @@ class Finder implements \IteratorAggregate, \Countable + * @return void + */ +- public static function addVCSPattern(string|array $pattern) ++ public static function addVCSPattern(string|array $pattern): void + { + foreach ((array) $pattern as $p) { +diff --git a/src/Symfony/Component/Form/AbstractExtension.php b/src/Symfony/Component/Form/AbstractExtension.php +index cffca7d398..f528c135a2 100644 +--- a/src/Symfony/Component/Form/AbstractExtension.php ++++ b/src/Symfony/Component/Form/AbstractExtension.php +@@ -99,5 +99,5 @@ abstract class AbstractExtension implements FormExtensionInterface + * @return FormTypeInterface[] + */ +- protected function loadTypes() ++ protected function loadTypes(): array + { + return []; +@@ -119,5 +119,5 @@ abstract class AbstractExtension implements FormExtensionInterface + * @return FormTypeGuesserInterface|null + */ +- protected function loadTypeGuesser() ++ protected function loadTypeGuesser(): ?FormTypeGuesserInterface + { + return null; +diff --git a/src/Symfony/Component/Form/AbstractRendererEngine.php b/src/Symfony/Component/Form/AbstractRendererEngine.php +index 3f1ab79c26..bddb459b86 100644 +--- a/src/Symfony/Component/Form/AbstractRendererEngine.php ++++ b/src/Symfony/Component/Form/AbstractRendererEngine.php +@@ -65,5 +65,5 @@ abstract class AbstractRendererEngine implements FormRendererEngineInterface, Re + * @return void + */ +- public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true) ++ public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true): void + { + $cacheKey = $view->vars[self::CACHE_KEY_VAR]; +@@ -128,5 +128,5 @@ abstract class AbstractRendererEngine implements FormRendererEngineInterface, Re + * @return bool + */ +- abstract protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName); ++ abstract protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName): bool; + + /** +diff --git a/src/Symfony/Component/Form/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php +index ad4b195696..ba4cf5c85b 100644 +--- a/src/Symfony/Component/Form/AbstractType.php ++++ b/src/Symfony/Component/Form/AbstractType.php +@@ -24,5 +24,5 @@ abstract class AbstractType implements FormTypeInterface + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + } +@@ -31,5 +31,5 @@ abstract class AbstractType implements FormTypeInterface + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + } +@@ -38,5 +38,5 @@ abstract class AbstractType implements FormTypeInterface + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options) ++ public function finishView(FormView $view, FormInterface $form, array $options): void + { + } +@@ -45,5 +45,5 @@ abstract class AbstractType implements FormTypeInterface + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + } +@@ -52,5 +52,5 @@ abstract class AbstractType implements FormTypeInterface + * @return string + */ +- public function getBlockPrefix() ++ public function getBlockPrefix(): string + { + return StringUtil::fqcnToBlockPrefix(static::class) ?: ''; +@@ -60,5 +60,5 @@ abstract class AbstractType implements FormTypeInterface + * @return string|null + */ +- public function getParent() ++ public function getParent(): ?string + { + return FormType::class; +diff --git a/src/Symfony/Component/Form/AbstractTypeExtension.php b/src/Symfony/Component/Form/AbstractTypeExtension.php +index 422f28bf33..b1d608fd4d 100644 +--- a/src/Symfony/Component/Form/AbstractTypeExtension.php ++++ b/src/Symfony/Component/Form/AbstractTypeExtension.php +@@ -22,5 +22,5 @@ abstract class AbstractTypeExtension implements FormTypeExtensionInterface + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + } +@@ -29,5 +29,5 @@ abstract class AbstractTypeExtension implements FormTypeExtensionInterface + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + } +@@ -36,5 +36,5 @@ abstract class AbstractTypeExtension implements FormTypeExtensionInterface + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options) ++ public function finishView(FormView $view, FormInterface $form, array $options): void + { + } +@@ -43,5 +43,5 @@ abstract class AbstractTypeExtension implements FormTypeExtensionInterface + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + } +diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php +index 20a30968d4..dbded1d7f3 100644 +--- a/src/Symfony/Component/Form/ButtonBuilder.php ++++ b/src/Symfony/Component/Form/ButtonBuilder.php +@@ -57,5 +57,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function add(string|FormBuilderInterface $child, string $type = null, array $options = []): static ++ public function add(string|FormBuilderInterface $child, string $type = null, array $options = []): never + { + throw new BadMethodCallException('Buttons cannot have children.'); +@@ -69,5 +69,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function create(string $name, string $type = null, array $options = []): FormBuilderInterface ++ public function create(string $name, string $type = null, array $options = []): never + { + throw new BadMethodCallException('Buttons cannot have children.'); +@@ -81,5 +81,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function get(string $name): FormBuilderInterface ++ public function get(string $name): never + { + throw new BadMethodCallException('Buttons cannot have children.'); +@@ -93,5 +93,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function remove(string $name): static ++ public function remove(string $name): never + { + throw new BadMethodCallException('Buttons cannot have children.'); +@@ -129,5 +129,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function addEventListener(string $eventName, callable $listener, int $priority = 0): static ++ public function addEventListener(string $eventName, callable $listener, int $priority = 0): never + { + throw new BadMethodCallException('Buttons do not support event listeners.'); +@@ -141,5 +141,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function addEventSubscriber(EventSubscriberInterface $subscriber): static ++ public function addEventSubscriber(EventSubscriberInterface $subscriber): never + { + throw new BadMethodCallException('Buttons do not support event subscribers.'); +@@ -153,5 +153,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function addViewTransformer(DataTransformerInterface $viewTransformer, bool $forcePrepend = false): static ++ public function addViewTransformer(DataTransformerInterface $viewTransformer, bool $forcePrepend = false): never + { + throw new BadMethodCallException('Buttons do not support data transformers.'); +@@ -165,5 +165,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function resetViewTransformers(): static ++ public function resetViewTransformers(): never + { + throw new BadMethodCallException('Buttons do not support data transformers.'); +@@ -177,5 +177,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function addModelTransformer(DataTransformerInterface $modelTransformer, bool $forceAppend = false): static ++ public function addModelTransformer(DataTransformerInterface $modelTransformer, bool $forceAppend = false): never + { + throw new BadMethodCallException('Buttons do not support data transformers.'); +@@ -189,5 +189,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function resetModelTransformers(): static ++ public function resetModelTransformers(): never + { + throw new BadMethodCallException('Buttons do not support data transformers.'); +@@ -221,5 +221,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setDataMapper(DataMapperInterface $dataMapper = null): static ++ public function setDataMapper(DataMapperInterface $dataMapper = null): never + { + if (1 > \func_num_args()) { +@@ -249,5 +249,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setEmptyData(mixed $emptyData): static ++ public function setEmptyData(mixed $emptyData): never + { + throw new BadMethodCallException('Buttons do not support empty data.'); +@@ -261,5 +261,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setErrorBubbling(bool $errorBubbling): static ++ public function setErrorBubbling(bool $errorBubbling): never + { + throw new BadMethodCallException('Buttons do not support error bubbling.'); +@@ -273,5 +273,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setRequired(bool $required): static ++ public function setRequired(bool $required): never + { + throw new BadMethodCallException('Buttons cannot be required.'); +@@ -285,5 +285,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setPropertyPath(string|PropertyPathInterface|null $propertyPath): static ++ public function setPropertyPath(string|PropertyPathInterface|null $propertyPath): never + { + throw new BadMethodCallException('Buttons do not support property paths.'); +@@ -297,5 +297,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setMapped(bool $mapped): static ++ public function setMapped(bool $mapped): never + { + throw new BadMethodCallException('Buttons do not support data mapping.'); +@@ -309,5 +309,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setByReference(bool $byReference): static ++ public function setByReference(bool $byReference): never + { + throw new BadMethodCallException('Buttons do not support data mapping.'); +@@ -321,5 +321,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setCompound(bool $compound): static ++ public function setCompound(bool $compound): never + { + throw new BadMethodCallException('Buttons cannot be compound.'); +@@ -345,5 +345,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setData(mixed $data): static ++ public function setData(mixed $data): never + { + throw new BadMethodCallException('Buttons do not support data.'); +@@ -357,5 +357,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setDataLocked(bool $locked): static ++ public function setDataLocked(bool $locked): never + { + throw new BadMethodCallException('Buttons do not support data locking.'); +@@ -369,5 +369,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setFormFactory(FormFactoryInterface $formFactory) ++ public function setFormFactory(FormFactoryInterface $formFactory): never + { + throw new BadMethodCallException('Buttons do not support form factories.'); +@@ -381,5 +381,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setAction(string $action): static ++ public function setAction(string $action): never + { + throw new BadMethodCallException('Buttons do not support actions.'); +@@ -393,5 +393,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setMethod(string $method): static ++ public function setMethod(string $method): never + { + throw new BadMethodCallException('Buttons do not support methods.'); +@@ -405,5 +405,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setRequestHandler(RequestHandlerInterface $requestHandler): static ++ public function setRequestHandler(RequestHandlerInterface $requestHandler): never + { + throw new BadMethodCallException('Buttons do not support request handlers.'); +@@ -433,5 +433,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setInheritData(bool $inheritData): static ++ public function setInheritData(bool $inheritData): never + { + throw new BadMethodCallException('Buttons do not support data inheritance.'); +@@ -457,5 +457,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function setIsEmptyCallback(?callable $isEmptyCallback): static ++ public function setIsEmptyCallback(?callable $isEmptyCallback): never + { + throw new BadMethodCallException('Buttons do not support "is empty" callback.'); +@@ -469,5 +469,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function getEventDispatcher(): EventDispatcherInterface ++ public function getEventDispatcher(): never + { + throw new BadMethodCallException('Buttons do not support event dispatching.'); +@@ -628,5 +628,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @return never + */ +- public function getFormFactory(): FormFactoryInterface ++ public function getFormFactory(): never + { + throw new BadMethodCallException('Buttons do not support adding children.'); +@@ -640,5 +640,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function getAction(): string ++ public function getAction(): never + { + throw new BadMethodCallException('Buttons do not support actions.'); +@@ -652,5 +652,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function getMethod(): string ++ public function getMethod(): never + { + throw new BadMethodCallException('Buttons do not support methods.'); +@@ -664,5 +664,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function getRequestHandler(): RequestHandlerInterface ++ public function getRequestHandler(): never + { + throw new BadMethodCallException('Buttons do not support request handlers.'); +@@ -716,5 +716,5 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface + * @throws BadMethodCallException + */ +- public function getIsEmptyCallback(): ?callable ++ public function getIsEmptyCallback(): never + { + throw new BadMethodCallException('Buttons do not support "is empty" callback.'); +diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php +index 40c0604ea4..34f7f441f2 100644 +--- a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php ++++ b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php +@@ -218,5 +218,5 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface, ResetInterf + * @return void + */ +- public function reset() ++ public function reset(): void + { + $this->lists = []; +diff --git a/src/Symfony/Component/Form/Command/DebugCommand.php b/src/Symfony/Component/Form/Command/DebugCommand.php +index 4a142e2965..dd99014998 100644 +--- a/src/Symfony/Component/Form/Command/DebugCommand.php ++++ b/src/Symfony/Component/Form/Command/DebugCommand.php +@@ -58,5 +58,5 @@ class DebugCommand extends Command + * @return void + */ +- protected function configure() ++ protected function configure(): void + { + $this +diff --git a/src/Symfony/Component/Form/DataMapperInterface.php b/src/Symfony/Component/Form/DataMapperInterface.php +index f04137aec6..4e874c8730 100644 +--- a/src/Symfony/Component/Form/DataMapperInterface.php ++++ b/src/Symfony/Component/Form/DataMapperInterface.php +@@ -30,5 +30,5 @@ interface DataMapperInterface + * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported + */ +- public function mapDataToForms(mixed $viewData, \Traversable $forms); ++ public function mapDataToForms(mixed $viewData, \Traversable $forms): void; + + /** +@@ -63,4 +63,4 @@ interface DataMapperInterface + * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported + */ +- public function mapFormsToData(\Traversable $forms, mixed &$viewData); ++ public function mapFormsToData(\Traversable $forms, mixed &$viewData): void; + } +diff --git a/src/Symfony/Component/Form/DataTransformerInterface.php b/src/Symfony/Component/Form/DataTransformerInterface.php +index 85fb99d218..6cc654f681 100644 +--- a/src/Symfony/Component/Form/DataTransformerInterface.php ++++ b/src/Symfony/Component/Form/DataTransformerInterface.php +@@ -65,5 +65,5 @@ interface DataTransformerInterface + * @throws TransformationFailedException when the transformation fails + */ +- public function transform(mixed $value); ++ public function transform(mixed $value): mixed; + + /** +@@ -96,4 +96,4 @@ interface DataTransformerInterface + * @throws TransformationFailedException when the transformation fails + */ +- public function reverseTransform(mixed $value); ++ public function reverseTransform(mixed $value): mixed; + } +diff --git a/src/Symfony/Component/Form/DependencyInjection/FormPass.php b/src/Symfony/Component/Form/DependencyInjection/FormPass.php +index efb6d5c8bb..ab3befa3f0 100644 +--- a/src/Symfony/Component/Form/DependencyInjection/FormPass.php ++++ b/src/Symfony/Component/Form/DependencyInjection/FormPass.php +@@ -34,5 +34,5 @@ class FormPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('form.extension')) { +diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php +index 119c81107d..cf9f6f16af 100644 +--- a/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php ++++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php +@@ -29,5 +29,5 @@ class CheckboxListMapper implements DataMapperInterface + * @return void + */ +- public function mapDataToForms(mixed $choices, \Traversable $checkboxes) ++ public function mapDataToForms(mixed $choices, \Traversable $checkboxes): void + { + if (!\is_array($choices ??= [])) { +@@ -44,5 +44,5 @@ class CheckboxListMapper implements DataMapperInterface + * @return void + */ +- public function mapFormsToData(\Traversable $checkboxes, mixed &$choices) ++ public function mapFormsToData(\Traversable $checkboxes, mixed &$choices): void + { + if (!\is_array($choices)) { +diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php +index 37fdba0c35..ed6557a3d8 100644 +--- a/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php ++++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php +@@ -29,5 +29,5 @@ class RadioListMapper implements DataMapperInterface + * @return void + */ +- public function mapDataToForms(mixed $choice, \Traversable $radios) ++ public function mapDataToForms(mixed $choice, \Traversable $radios): void + { + if (!\is_string($choice)) { +@@ -44,5 +44,5 @@ class RadioListMapper implements DataMapperInterface + * @return void + */ +- public function mapFormsToData(\Traversable $radios, mixed &$choice) ++ public function mapFormsToData(\Traversable $radios, mixed &$choice): void + { + if (null !== $choice && !\is_string($choice)) { +diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php +index 7189977549..29ec9e3efc 100644 +--- a/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php ++++ b/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php +@@ -36,5 +36,5 @@ class FixUrlProtocolListener implements EventSubscriberInterface + * @return void + */ +- public function onSubmit(FormEvent $event) ++ public function onSubmit(FormEvent $event): void + { + $data = $event->getData(); +diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php +index 62cd0a42a7..55ab20aabc 100644 +--- a/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php ++++ b/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php +@@ -45,5 +45,5 @@ class MergeCollectionListener implements EventSubscriberInterface + * @return void + */ +- public function onSubmit(FormEvent $event) ++ public function onSubmit(FormEvent $event): void + { + $dataToMergeInto = $event->getForm()->getNormData(); +diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php +index cec439754e..0ef6b26c3e 100644 +--- a/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php ++++ b/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php +@@ -56,5 +56,5 @@ class ResizeFormListener implements EventSubscriberInterface + * @return void + */ +- public function preSetData(FormEvent $event) ++ public function preSetData(FormEvent $event): void + { + $form = $event->getForm(); +@@ -81,5 +81,5 @@ class ResizeFormListener implements EventSubscriberInterface + * @return void + */ +- public function preSubmit(FormEvent $event) ++ public function preSubmit(FormEvent $event): void + { + $form = $event->getForm(); +@@ -114,5 +114,5 @@ class ResizeFormListener implements EventSubscriberInterface + * @return void + */ +- public function onSubmit(FormEvent $event) ++ public function onSubmit(FormEvent $event): void + { + $form = $event->getForm(); +diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php +index c9c216b59f..82b8cfb33b 100644 +--- a/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php ++++ b/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php +@@ -40,5 +40,5 @@ class TransformationFailureListener implements EventSubscriberInterface + * @return void + */ +- public function convertTransformationFailureToFormError(FormEvent $event) ++ public function convertTransformationFailureToFormError(FormEvent $event): void + { + $form = $event->getForm(); +diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php +index 81a55f3cb0..ea669fb590 100644 +--- a/src/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php ++++ b/src/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php +@@ -27,5 +27,5 @@ class TrimListener implements EventSubscriberInterface + * @return void + */ +- public function preSubmit(FormEvent $event) ++ public function preSubmit(FormEvent $event): void + { + $data = $event->getData(); +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php +index 5e2ae22481..760cb1a132 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php +@@ -33,5 +33,5 @@ abstract class BaseType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->setDisabled($options['disabled']); +@@ -42,5 +42,5 @@ abstract class BaseType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $name = $form->getName(); +@@ -129,5 +129,5 @@ abstract class BaseType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php b/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php +index fa60d016eb..fdb786cc61 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php +@@ -20,5 +20,5 @@ class BirthdayType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php +index d710546407..5ff4dc9989 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php +@@ -35,5 +35,5 @@ class ButtonType extends BaseType implements ButtonTypeInterface + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + parent::configureOptions($resolver); +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php +index 291ede93ef..a4128a3880 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php +@@ -24,5 +24,5 @@ class CheckboxType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + // Unlike in other types, where the data is NULL by default, it +@@ -39,5 +39,5 @@ class CheckboxType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars = array_replace($view->vars, [ +@@ -50,5 +50,5 @@ class CheckboxType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $emptyData = static fn (FormInterface $form, $viewData) => $viewData; +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +index 4dcd3b6877..16e18de555 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +@@ -66,5 +66,5 @@ class ChoiceType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $unknownValues = []; +@@ -222,5 +222,5 @@ class ChoiceType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $choiceTranslationDomain = $options['choice_translation_domain']; +@@ -279,5 +279,5 @@ class ChoiceType extends AbstractType + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options) ++ public function finishView(FormView $view, FormInterface $form, array $options): void + { + if ($options['expanded']) { +@@ -299,5 +299,5 @@ class ChoiceType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $emptyData = static function (Options $options) { +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php +index 0216e61dd5..0ca3eebc0b 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php +@@ -25,5 +25,5 @@ class CollectionType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $resizePrototypeOptions = null; +@@ -58,5 +58,5 @@ class CollectionType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars = array_replace($view->vars, [ +@@ -74,5 +74,5 @@ class CollectionType extends AbstractType + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options) ++ public function finishView(FormView $view, FormInterface $form, array $options): void + { + $prefixOffset = -2; +@@ -108,5 +108,5 @@ class CollectionType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $entryOptionsNormalizer = static function (Options $options, $value) { +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php b/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php +index 31538fc3c7..de208cdade 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php +@@ -37,5 +37,5 @@ class ColorType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!$options['html5']) { +@@ -67,5 +67,5 @@ class ColorType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +index 6f872660a0..f352beb90d 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +@@ -26,5 +26,5 @@ class CountryType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +index 89edc6f630..fc06bbb299 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +@@ -26,5 +26,5 @@ class CurrencyType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php +index 655ef6682f..0e525d09f6 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php +@@ -47,5 +47,5 @@ class DateIntervalType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!$options['with_years'] && !$options['with_months'] && !$options['with_weeks'] && !$options['with_days'] && !$options['with_hours'] && !$options['with_minutes'] && !$options['with_seconds']) { +@@ -152,5 +152,5 @@ class DateIntervalType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $vars = [ +@@ -167,5 +167,5 @@ class DateIntervalType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +index 32c58447cd..ec35f6ee41 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +@@ -51,5 +51,5 @@ class DateTimeType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $parts = ['year', 'month', 'day', 'hour']; +@@ -200,5 +200,5 @@ class DateTimeType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['widget'] = $options['widget']; +@@ -228,5 +228,5 @@ class DateTimeType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +index 3b68dc241a..474139a6ee 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +@@ -47,5 +47,5 @@ class DateType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $dateFormat = \is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT; +@@ -184,5 +184,5 @@ class DateType extends AbstractType + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options) ++ public function finishView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['widget'] = $options['widget']; +@@ -224,5 +224,5 @@ class DateType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php b/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php +index 64d01ee67a..0cd6cd3b79 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php +@@ -20,5 +20,5 @@ class EmailType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +index cf8e1a7439..2ee4fa5fea 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +@@ -45,5 +45,5 @@ class FileType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + // Ensure that submitted data is always an uploaded file or an array of some +@@ -89,5 +89,5 @@ class FileType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + if ($options['multiple']) { +@@ -105,5 +105,5 @@ class FileType extends AbstractType + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options) ++ public function finishView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['multipart'] = true; +@@ -113,5 +113,5 @@ class FileType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $dataClass = null; +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +index 82aa77f0a3..f3abe461c9 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +@@ -42,5 +42,5 @@ class FormType extends BaseType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + parent::buildForm($builder, $options); +@@ -73,5 +73,5 @@ class FormType extends BaseType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + parent::buildView($view, $form, $options); +@@ -115,5 +115,5 @@ class FormType extends BaseType + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options) ++ public function finishView(FormView $view, FormInterface $form, array $options): void + { + $multipart = false; +@@ -132,5 +132,5 @@ class FormType extends BaseType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + parent::configureOptions($resolver); +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php b/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php +index c4e5eb2ccf..495525f889 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php +@@ -20,5 +20,5 @@ class HiddenType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php b/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php +index a287b66b7c..12dc4a1f71 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php +@@ -24,5 +24,5 @@ class IntegerType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addViewTransformer(new IntegerToLocalizedStringTransformer($options['grouping'], $options['rounding_mode'], !$options['grouping'] ? 'en' : null)); +@@ -32,5 +32,5 @@ class IntegerType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + if ($options['grouping']) { +@@ -42,5 +42,5 @@ class IntegerType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php +index eeb9e591a1..b0eb640a5f 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php +@@ -27,5 +27,5 @@ class LanguageType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +index e98134febd..9f2662031e 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +@@ -26,5 +26,5 @@ class LocaleType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php b/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php +index 9c9e5b4d7c..c8d4ecf232 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php +@@ -28,5 +28,5 @@ class MoneyType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + // Values used in HTML5 number inputs should be formatted as in "1234.5", ie. 'en' format without grouping, +@@ -46,5 +46,5 @@ class MoneyType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['money_pattern'] = self::getPattern($options['currency']); +@@ -58,5 +58,5 @@ class MoneyType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +@@ -107,5 +107,5 @@ class MoneyType extends AbstractType + * @return string + */ +- protected static function getPattern(?string $currency) ++ protected static function getPattern(?string $currency): string + { + if (!$currency) { +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php b/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php +index 578991f9fd..16f39e873b 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php +@@ -27,5 +27,5 @@ class NumberType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addViewTransformer(new NumberToLocalizedStringTransformer( +@@ -44,5 +44,5 @@ class NumberType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + if ($options['html5']) { +@@ -60,5 +60,5 @@ class NumberType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php b/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php +index 0c247f0f30..08d7cefb9a 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php +@@ -22,5 +22,5 @@ class PasswordType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + if ($options['always_empty'] || !$form->isSubmitted()) { +@@ -32,5 +32,5 @@ class PasswordType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php b/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php +index f71e288b3e..30fad82d81 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php +@@ -24,5 +24,5 @@ class PercentType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addViewTransformer(new PercentToLocalizedStringTransformer( +@@ -37,5 +37,5 @@ class PercentType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['symbol'] = $options['symbol']; +@@ -49,5 +49,5 @@ class PercentType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php b/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php +index 4b97b0ae21..1889bb0e1e 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php +@@ -20,5 +20,5 @@ class RadioType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php b/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php +index 2e33a977d9..ed7e88b5af 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php +@@ -20,5 +20,5 @@ class RangeType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php b/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php +index 4176f93e52..8f133ee41d 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php +@@ -22,5 +22,5 @@ class RepeatedType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + // Overwrite required option for child fields +@@ -48,5 +48,5 @@ class RepeatedType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php b/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php +index 0dca6e42a8..aa51e40efa 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php +@@ -20,5 +20,5 @@ class SearchType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php b/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php +index 3f1b5f95c9..d586bb1463 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php +@@ -28,5 +28,5 @@ class SubmitType extends AbstractType implements SubmitButtonTypeInterface + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['clicked'] = $form->isClicked(); +@@ -40,5 +40,5 @@ class SubmitType extends AbstractType implements SubmitButtonTypeInterface + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefault('validate', true); +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TelType.php b/src/Symfony/Component/Form/Extension/Core/Type/TelType.php +index 05fdd41626..b6565675ee 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/TelType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/TelType.php +@@ -20,5 +20,5 @@ class TelType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TextType.php b/src/Symfony/Component/Form/Extension/Core/Type/TextType.php +index 479ce054d8..e909f2fa3b 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/TextType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/TextType.php +@@ -22,5 +22,5 @@ class TextType extends AbstractType implements DataTransformerInterface + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + // When empty_data is explicitly set to an empty string, +@@ -37,5 +37,5 @@ class TextType extends AbstractType implements DataTransformerInterface + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php b/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php +index 40e7580d80..18c58e30c0 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php +@@ -21,5 +21,5 @@ class TextareaType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['pattern'] = null; +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +index c7d5276960..25d2444547 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +@@ -38,5 +38,5 @@ class TimeType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $parts = ['hour']; +@@ -214,5 +214,5 @@ class TimeType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars = array_replace($view->vars, [ +@@ -245,5 +245,5 @@ class TimeType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +index a5d4bc61c0..b104414403 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +@@ -29,5 +29,5 @@ class TimezoneType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + if ('datetimezone' === $options['input']) { +@@ -41,5 +41,5 @@ class TimezoneType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php b/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php +index 029ad4d439..3814f6ada0 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php +@@ -32,5 +32,5 @@ class TransformationFailureExtension extends AbstractTypeExtension + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!isset($options['constraints'])) { +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/UlidType.php b/src/Symfony/Component/Form/Extension/Core/Type/UlidType.php +index ea3da07c02..78b57ad153 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/UlidType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/UlidType.php +@@ -25,5 +25,5 @@ class UlidType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder +@@ -35,5 +35,5 @@ class UlidType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php b/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php +index 385c7a25fa..4a8fcc71f2 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php +@@ -24,5 +24,5 @@ class UrlType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (null !== $options['default_protocol']) { +@@ -34,5 +34,5 @@ class UrlType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + if ($options['default_protocol']) { +@@ -45,5 +45,5 @@ class UrlType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/UuidType.php b/src/Symfony/Component/Form/Extension/Core/Type/UuidType.php +index 7c0f65b9a0..d79b4d30e6 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/UuidType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/UuidType.php +@@ -25,5 +25,5 @@ class UuidType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder +@@ -35,5 +35,5 @@ class UuidType extends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php b/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php +index 8027a41a99..9ffba28dac 100644 +--- a/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php ++++ b/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php +@@ -32,5 +32,5 @@ class WeekType extends AbstractType + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + if ('string' === $options['input']) { +@@ -87,5 +87,5 @@ class WeekType extends AbstractType + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['widget'] = $options['widget']; +@@ -99,5 +99,5 @@ class WeekType ex 8000 tends AbstractType + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; +diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +index eca450a165..72330772b9 100644 +--- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php ++++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +@@ -55,5 +55,5 @@ class CsrfValidationListener implements EventSubscriberInterface + * @return void + */ +- public function preSubmit(FormEvent $event) ++ public function preSubmit(FormEvent $event): void + { + $form = $event->getForm(); +diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +index 8c3d45dec0..ff7934deed 100644 +--- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php ++++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +@@ -51,5 +51,5 @@ class FormTypeCsrfExtension extends AbstractTypeExtension + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!$options['csrf_protection']) { +@@ -75,5 +75,5 @@ class FormTypeCsrfExtension extends AbstractTypeExtension + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options) ++ public function finishView(FormView $view, FormInterface $form, array $options): void + { + if ($options['csrf_protection'] && !$view->parent && $options['compound']) { +@@ -94,5 +94,5 @@ class FormTypeCsrfExtension extends AbstractTypeExtension + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php b/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php +index 41a52e091e..fce936b740 100644 +--- a/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php ++++ b/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php +@@ -47,5 +47,5 @@ class DataCollectorListener implements EventSubscriberInterface + * @return void + */ +- public function postSetData(FormEvent $event) ++ public function postSetData(FormEvent $event): void + { + if ($event->getForm()->isRoot()) { +@@ -63,5 +63,5 @@ class DataCollectorListener implements EventSubscriberInterface + * @return void + */ +- public function postSubmit(FormEvent $event) ++ public function postSubmit(FormEvent $event): void + { + if ($event->getForm()->isRoot()) { +diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php +index 346c101fe3..40ed4b5e8f 100644 +--- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php ++++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php +@@ -29,5 +29,5 @@ interface FormDataCollectorInterface extends DataCollectorInterface + * @return void + */ +- public function collectConfiguration(FormInterface $form); ++ public function collectConfiguration(FormInterface $form): void; + + /** +@@ -36,5 +36,5 @@ interface FormDataCollectorInterface extends DataCollectorInterface + * @return void + */ +- public function collectDefaultData(FormInterface $form); ++ public function collectDefaultData(FormInterface $form): void; + + /** +@@ -43,5 +43,5 @@ interface FormDataCollectorInterface extends DataCollectorInterface + * @return void + */ +- public function collectSubmittedData(FormInterface $form); ++ public function collectSubmittedData(FormInterface $form): void; + + /** +@@ -50,5 +50,5 @@ interface FormDataCollectorInterface extends DataCollectorInterface + * @return void + */ +- public function collectViewVariables(FormView $view); ++ public function collectViewVariables(FormView $view): void; + + /** +@@ -57,5 +57,5 @@ interface FormDataCollectorInterface extends DataCollectorInterface + * @return void + */ +- public function associateFormWithView(FormInterface $form, FormView $view); ++ public function associateFormWithView(FormInterface $form, FormView $view): void; + + /** +@@ -67,5 +67,5 @@ interface FormDataCollectorInterface extends DataCollectorInterface + * @return void + */ +- public function buildPreliminaryFormTree(FormInterface $form); ++ public function buildPreliminaryFormTree(FormInterface $form): void; + + /** +@@ -89,5 +89,5 @@ interface FormDataCollectorInterface extends DataCollectorInterface + * @return void + */ +- public function buildFinalFormTree(FormInterface $form, FormView $view); ++ public function buildFinalFormTree(FormInterface $form, FormView $view): void; + + /** +diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php +index 6c8cf3ee24..0d8fba357e 100644 +--- a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php ++++ b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php +@@ -75,5 +75,5 @@ class ResolvedTypeDataCollectorProxy implements ResolvedFormTypeInterface + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $this->proxiedType->buildForm($builder, $options); +@@ -83,5 +83,5 @@ class ResolvedTypeDataCollectorProxy implements ResolvedFormTypeInterface + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $this->proxiedType->buildView($view, $form, $options); +@@ -91,5 +91,5 @@ class ResolvedTypeDataCollectorProxy implements ResolvedFormTypeInterface + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options) ++ public function finishView(FormView $view, FormInterface $form, array $options): void + { + $this->proxiedType->finishView($view, $form, $options); +diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php b/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php +index f1e3c903ec..20721f3040 100644 +--- a/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php ++++ b/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php +@@ -36,5 +36,5 @@ class DataCollectorTypeExtension extends AbstractTypeExtension + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addEventSubscriber($this->listener); +diff --git a/src/Symfony/Component/Form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php b/src/Symfony/Component/Form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php +index 8e92ea74a5..633d1985db 100644 +--- a/src/Symfony/Component/Form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php ++++ b/src/Symfony/Component/Form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php +@@ -39,5 +39,5 @@ class TextTypeHtmlSanitizerExtension extends AbstractTypeExtension + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver +@@ -51,5 +51,5 @@ class TextTypeHtmlSanitizerExtension extends AbstractTypeExtension + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + if (!$options['sanitize_html']) { +diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +index b4e835c95a..9cbcc28e31 100644 +--- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php ++++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +@@ -39,5 +39,5 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface + * @return void + */ +- public function handleRequest(FormInterface $form, mixed $request = null) ++ public function handleRequest(FormInterface $form, mixed $request = null): void + { + if (!$request instanceof Request) { +diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php b/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php +index cc3e5e1207..f9c85b9a0a 100644 +--- a/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php ++++ b/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php +@@ -33,5 +33,5 @@ class FormTypeHttpFoundationExtension extends AbstractTypeExtension + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->setRequestHandler($this->requestHandler); +diff --git a/src/Symfony/Component/Form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php b/src/Symfony/Component/Form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php +index 4854dd3e73..b61c5664f6 100644 +--- a/src/Symfony/Component/Form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php ++++ b/src/Symfony/Component/Form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php +@@ -39,5 +39,5 @@ class PasswordHasherListener + * @return void + */ +- public function registerPassword(FormEvent $event) ++ public function registerPassword(FormEvent $event): void + { + if (null === $event->getData() || '' === $event->getData()) { +@@ -57,5 +57,5 @@ class PasswordHasherListener + * @return void + */ +- public function hashPasswords(FormEvent $event) ++ public function hashPasswords(FormEvent $event): void + { + $form = $event->getForm(); +diff --git a/src/Symfony/Component/Form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php b/src/Symfony/Component/Form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php +index 5308992863..e799b6ba31 100644 +--- a/src/Symfony/Component/Form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php ++++ b/src/Symfony/Component/Form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php +@@ -31,5 +31,5 @@ class FormTypePasswordHasherExtension extends AbstractTypeExtension + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addEventListener(FormEvents::POST_SUBMIT, [$this->passwordHasherListener, 'hashPasswords']); +diff --git a/src/Symfony/Component/Form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php b/src/Symfony/Component/Form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php +index 6f022fb1bf..aede2bbb52 100644 +--- a/src/Symfony/Component/Form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php ++++ b/src/Symfony/Component/Form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php +@@ -33,5 +33,5 @@ class PasswordTypePasswordHasherExtension extends AbstractTypeExtension + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + if ($options['hash_property_path']) { +@@ -43,5 +43,5 @@ class PasswordTypePasswordHasherExtension extends AbstractTypeExtension + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ +diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +index d664e9b500..fbff1c16bf 100644 +--- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php ++++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +@@ -33,5 +33,5 @@ class FormValidator extends ConstraintValidator + * @return void + */ +- public function validate(mixed $form, Constraint $formConstraint) ++ public function validate(mixed $form, Constraint $formConstraint): void + { + if (!$formConstraint instanceof Form) { +diff --git a/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php b/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php +index e2d4357622..ed84c8b4cf 100644 +--- a/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php ++++ b/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php +@@ -41,5 +41,5 @@ class ValidationListener implements EventSubscriberInterface + * @return void + */ +- public function validateForm(FormEvent $event) ++ public function validateForm(FormEvent $event): void + { + $form = $event->getForm(); +diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php +index ea01d03699..e9c7e410af 100644 +--- a/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php ++++ b/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php +@@ -28,5 +28,5 @@ abstract class BaseValidatorExtension extends AbstractTypeExtension + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + // Make sure that validation groups end up as null, closure or array +diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php +index 54eebaf63e..e6be9e5266 100644 +--- a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php ++++ b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php +@@ -41,5 +41,5 @@ class FormTypeValidatorExtension extends BaseValidatorExtension + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addEventSubscriber(new ValidationListener($this->validator, $this->violationMapper)); +@@ -49,5 +49,5 @@ class FormTypeValidatorExtension extends BaseValidatorExtension + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + parent::configureOptions($resolver); +diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php +index d41dc0168c..0cb2b3c6c8 100644 +--- a/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php ++++ b/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php +@@ -25,5 +25,5 @@ class RepeatedTypeValidatorExtension extends AbstractTypeExtension + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + // Map errors to the first field +diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php +index b7a19ed26a..30c8b9e57c 100644 +--- a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php ++++ b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php +@@ -36,5 +36,5 @@ class UploadValidatorExtension extends AbstractTypeExtension + * @return void + */ +- public function configureOptions(OptionsResolver $resolver) ++ public function configureOptions(OptionsResolver $resolver): void + { + $translator = $this->translator; +diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php +index 2f2ccefd30..689a6d5cab 100644 +--- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php ++++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php +@@ -42,5 +42,5 @@ class ViolationMapper implements ViolationMapperInterface + * @return void + */ +- public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false) ++ public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false): void + { + $this->allowNonSynchronized = $allowNonSynchronized; +diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php +index a72d41df9e..6a58a1bddc 100644 +--- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php ++++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php +@@ -28,4 +28,4 @@ interface ViolationMapperInterface + * @return void + */ +- public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false); ++ public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false): void; + } +diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php +index ed363a7b15..967d95cc27 100644 +--- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php ++++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php +@@ -27,5 +27,5 @@ class ViolationPathIterator extends PropertyPathIterator + * @return bool + */ +- public function mapsForm() ++ public function mapsForm(): bool + { + return $this->path->mapsForm($this->key()); +diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php +index 9fed3d1a0a..5cd4eb5f16 100644 +--- a/src/Symfony/Component/Form/FormConfigBuilder.php ++++ b/src/Symfony/Component/Form/FormConfigBuilder.php +@@ -537,5 +537,5 @@ class FormConfigBuilder implements FormConfigBuilderInterface + * @return $this + */ +- public function setFormFactory(FormFactoryInterface $formFactory) ++ public function setFormFactory(FormFactoryInterface $formFactory): static + { + if ($this->locked) { +diff --git a/src/Symfony/Component/Form/FormConfigBuilderInterface.php b/src/Symfony/Component/Form/FormConfigBuilderInterface.php +index 09b9149801..6b22b9f9e8 100644 +--- a/src/Symfony/Component/Form/FormConfigBuilderInterface.php ++++ b/src/Symfony/Component/Form/FormConfigBuilderInterface.php +@@ -209,5 +209,5 @@ interface FormConfigBuilderInterface extends FormConfigInterface + * @return $this + */ +- public function setFormFactory(FormFactoryInterface $formFactory); ++ public function setFormFactory(FormFactoryInterface $formFactory): static; + + /** +diff --git a/src/Symfony/Component/Form/FormError.php b/src/Symfony/Component/Form/FormError.php +index 572783c7ac..37d609af00 100644 +--- a/src/Symfony/Component/Form/FormError.php ++++ b/src/Symfony/Component/Form/FormError.php +@@ -104,5 +104,5 @@ class FormError + * @throws BadMethodCallException If the method is called more than once + */ +- public function setOrigin(FormInterface $origin) ++ public function setOrigin(FormInterface $origin): void + { + if (null !== $this->origin) { +diff --git a/src/Symfony/Component/Form/FormEvent.php b/src/Symfony/Component/Form/FormEvent.php +index 1e6aa34d63..3f05ff971a 100644 +--- a/src/Symfony/Component/Form/FormEvent.php ++++ b/src/Symfony/Component/Form/FormEvent.php +@@ -49,5 +49,5 @@ class FormEvent extends Event + * @return void + */ +- public function setData(mixed $data) ++ public function setData(mixed $data): void + { + $this->data = $data; +diff --git a/src/Symfony/Component/Form/FormRenderer.php b/src/Symfony/Component/Form/FormRenderer.php +index 18dec4946b..9272277b5c 100644 +--- a/src/Symfony/Component/Form/FormRenderer.php ++++ b/src/Symfony/Component/Form/FormRenderer.php +@@ -46,5 +46,5 @@ class FormRenderer implements FormRendererInterface + * @return void + */ +- public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true) ++ public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true): void + { + $this->engine->setTheme($view, $themes, $useDefaultThemes); +diff --git a/src/Symfony/Component/Form/FormRendererEngineInterface.php b/src/Symfony/Component/Form/FormRendererEngineInterface.php +index e7de3544a1..739c7b835e 100644 +--- a/src/Symfony/Component/Form/FormRendererEngineInterface.php ++++ b/src/Symfony/Component/Form/FormRendererEngineInterface.php +@@ -28,5 +28,5 @@ interface FormRendererEngineInterface + * @return void + */ +- public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true); ++ public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true): void; + + /** +@@ -133,4 +133,4 @@ interface FormRendererEngineInterface + * @return string + */ +- public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []); ++ public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []): string; + } +diff --git a/src/Symfony/Component/Form/FormRendererInterface.php b/src/Symfony/Component/Form/FormRendererInterface.php +index 8e805727ce..188a668c77 100644 +--- a/src/Symfony/Component/Form/FormRendererInterface.php ++++ b/src/Symfony/Component/Form/FormRendererInterface.php +@@ -35,5 +35,5 @@ interface FormRendererInterface + * @return void + */ +- public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true); ++ public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true): void; + + /** +diff --git a/src/Symfony/Component/Form/FormTypeExtensionInterface.php b/src/Symfony/Component/Form/FormTypeExtensionInterface.php +index 1937834515..25712a13d7 100644 +--- a/src/Symfony/Component/Form/FormTypeExtensionInterface.php ++++ b/src/Symfony/Component/Form/FormTypeExtensionInterface.php +@@ -31,5 +31,5 @@ interface FormTypeExtensionInterface + * @see FormTypeInterface::buildForm() + */ +- public function buildForm(FormBuilderInterface $builder, array $options); ++ public function buildForm(FormBuilderInterface $builder, array $options): void; + + /** +@@ -45,5 +45,5 @@ interface FormTypeExtensionInterface + * @see FormTypeInterface::buildView() + */ +- public function buildView(FormView $view, FormInterface $form, array $options); ++ public function buildView(FormView $view, FormInterface $form, array $options): void; + + /** +@@ -59,10 +59,10 @@ interface FormTypeExtensionInterface + * @see FormTypeInterface::finishView() + */ +- public function finishView(FormView $view, FormInterface $form, array $options); ++ public function finishView(FormView $view, FormInterface $form, array $options): void; + + /** + * @return void + */ +- public function configureOptions(OptionsResolver $resolver); ++ public function configureOptions(OptionsResolver $resolver): void; + + /** +diff --git a/src/Symfony/Component/Form/FormTypeGuesserInterface.php b/src/Symfony/Component/Form/FormTypeGuesserInterface.php +index 61e2c5f80d..4d6b335474 100644 +--- a/src/Symfony/Component/Form/FormTypeGuesserInterface.php ++++ b/src/Symfony/Component/Form/FormTypeGuesserInterface.php +@@ -22,5 +22,5 @@ interface FormTypeGuesserInterface + * @return Guess\TypeGuess|null + */ +- public function guessType(string $class, string $property); ++ public function guessType(string $class, string $property): ?Guess\TypeGuess; + + /** +@@ -29,5 +29,5 @@ interface FormTypeGuesserInterface + * @return Guess\ValueGuess|null + */ +- public function guessRequired(string $class, string $property); ++ public function guessRequired(string $class, string $property): ?Guess\ValueGuess; + + /** +@@ -36,5 +36,5 @@ interface FormTypeGuesserInterface + * @return Guess\ValueGuess|null + */ +- public function guessMaxLength(string $class, string $property); ++ public function guessMaxLength(string $class, string $property): ?Guess\ValueGuess; + + /** +@@ -50,4 +50,4 @@ interface FormTypeGuesserInterface + * @return Guess\ValueGuess|null + */ +- public function guessPattern(string $class, string $property); ++ public function guessPattern(string $class, string $property): ?Guess\ValueGuess; + } +diff --git a/src/Symfony/Component/Form/FormTypeInterface.php b/src/Symfony/Component/Form/FormTypeInterface.php +index 0c586d3f71..6c625cf403 100644 +--- a/src/Symfony/Component/Form/FormTypeInterface.php ++++ b/src/Symfony/Component/Form/FormTypeInterface.php +@@ -31,5 +31,5 @@ interface FormTypeInterface + * @see FormTypeExtensionInterface::buildForm() + */ +- public function buildForm(FormBuilderInterface $builder, array $options); ++ public function buildForm(FormBuilderInterface $builder, array $options): void; + + /** +@@ -49,5 +49,5 @@ interface FormTypeInterface + * @see FormTypeExtensionInterface::buildView() + */ +- public function buildView(FormView $view, FormInterface $form, array $options); ++ public function buildView(FormView $view, FormInterface $form, array $options): void; + + /** +@@ -68,5 +68,5 @@ interface FormTypeInterface + * @see FormTypeExtensionInterface::finishView() + */ +- public function finishView(FormView $view, FormInterface $form, array $options); ++ public function finishView(FormView $view, FormInterface $form, array $options): void; + + /** +@@ -75,5 +75,5 @@ interface FormTypeInterface + * @return void + */ +- public function configureOptions(OptionsResolver $resolver); ++ public function configureOptions(OptionsResolver $resolver): void; + + /** +@@ -85,5 +85,5 @@ interface FormTypeInterface + * @return string + */ +- public function getBlockPrefix(); ++ public function getBlockPrefix(): string; + + /** +@@ -92,4 +92,4 @@ interface FormTypeInterface + * @return string|null + */ +- public function getParent(); ++ public function getParent(): ?string; + } +diff --git a/src/Symfony/Component/Form/FormView.php b/src/Symfony/Component/Form/FormView.php +index e04fa13b09..231b75810e 100644 +--- a/src/Symfony/Component/Form/FormView.php ++++ b/src/Symfony/Component/Form/FormView.php +@@ -96,5 +96,5 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable + * @return void + */ +- public function setMethodRendered() ++ public function setMethodRendered(): void + { + $this->methodRendered = true; +diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php +index 11c4d4d9c0..a09361d59a 100644 +--- a/src/Symfony/Component/Form/NativeRequestHandler.php ++++ b/src/Symfony/Component/Form/NativeRequestHandler.php +@@ -45,5 +45,5 @@ class NativeRequestHandler implements RequestHandlerInterface + * @throws Exception\UnexpectedTypeException If the $request is not null + */ +- public function handleRequest(FormInterface $form, mixed $request = null) ++ public function handleRequest(FormInterface $form, mixed $request = null): void + { + if (null !== $request) { +diff --git a/src/Symfony/Component/Form/RequestHandlerInterface.php b/src/Symfony/Component/Form/RequestHandlerInterface.php +index 39fd458ee4..ccd77ccecc 100644 +--- a/src/Symfony/Component/Form/RequestHandlerInterface.php ++++ b/src/Symfony/Component/Form/RequestHandlerInterface.php +@@ -24,5 +24,5 @@ interface RequestHandlerInterface + * @return void + */ +- public function handleRequest(FormInterface $form, mixed $request = null); ++ public function handleRequest(FormInterface $form, mixed $request = null): void; + + /** +diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php +index f05db1533b..0e67c63c66 100644 +--- a/src/Symfony/Component/Form/ResolvedFormType.php ++++ b/src/Symfony/Component/Form/ResolvedFormType.php +@@ -96,5 +96,5 @@ class ResolvedFormType implements ResolvedFormTypeInterface + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options) ++ public function buildForm(FormBuilderInterface $builder, array $options): void + { + $this->parent?->buildForm($builder, $options); +@@ -110,5 +110,5 @@ class ResolvedFormType implements ResolvedFormTypeInterface + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options) ++ public function buildView(FormView $view, FormInterface $form, array $options): void + { + $this->parent?->buildView($view, $form, $options); +@@ -124,5 +124,5 @@ class ResolvedFormType implements ResolvedFormTypeInterface + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options) ++ public function finishView(FormView $view, FormInterface $form, array $options): void + { + $this->parent?->finishView($view, $form, $options); +diff --git a/src/Symfony/Component/Form/ResolvedFormTypeInterface.php b/src/Symfony/Component/Form/ResolvedFormTypeInterface.php +index e0b96a5ac3..7982e0cab3 100644 +--- a/src/Symfony/Component/Form/ResolvedFormTypeInterface.php ++++ b/src/Symfony/Component/Form/ResolvedFormTypeInterface.php +@@ -60,5 +60,5 @@ interface ResolvedFormTypeInterface + * @return void + */ +- public function buildForm(FormBuilderInterface $builder, array $options); ++ public function buildForm(FormBuilderInterface $builder, array $options): void; + + /** +@@ -69,5 +69,5 @@ interface ResolvedFormTypeInterface + * @return void + */ +- public function buildView(FormView $view, FormInterface $form, array $options); ++ 8000 public function buildView(FormView $view, FormInterface $form, array $options): void; + + /** +@@ -78,5 +78,5 @@ interface ResolvedFormTypeInterface + * @return void + */ +- public function finishView(FormView $view, FormInterface $form, array $options); ++ public function finishView(FormView $view, FormInterface $form, array $options): void; + + /** +diff --git a/src/Symfony/Component/HttpClient/CachingHttpClient.php b/src/Symfony/Component/HttpClient/CachingHttpClient.php +index 0b6e495806..8cef2cad7c 100644 +--- a/src/Symfony/Component/HttpClient/CachingHttpClient.php ++++ b/src/Symfony/Component/HttpClient/CachingHttpClient.php +@@ -140,5 +140,5 @@ class CachingHttpClient implements HttpClientInterface, ResetInterface + * @return void + */ +- public function reset() ++ public function reset(): void + { + if ($this->client instanceof ResetInterface) { +diff --git a/src/Symfony/Component/HttpClient/DecoratorTrait.php b/src/Symfony/Component/HttpClient/DecoratorTrait.php +index 472437e465..1dfe39146b 100644 +--- a/src/Symfony/Component/HttpClient/DecoratorTrait.php ++++ b/src/Symfony/Component/HttpClient/DecoratorTrait.php +@@ -52,5 +52,5 @@ trait DecoratorTrait + * @return void + */ +- public function reset() ++ public function reset(): void + { + if ($this->client instanceof ResetInterface) { +diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php +index c767ca81aa..51313be49f 100644 +--- a/src/Symfony/Component/HttpClient/HttpClientTrait.php ++++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php +@@ -669,5 +669,5 @@ trait HttpClientTrait + * @return string + */ +- private static function removeDotSegments(string $path) ++ private static function removeDotSegments(string $path): string + { + $result = ''; +diff --git a/src/Symfony/Component/HttpClient/MockHttpClient.php b/src/Symfony/Component/HttpClient/MockHttpClient.php +index 5d8a2dccff..300c56dc6d 100644 +--- a/src/Symfony/Component/HttpClient/MockHttpClient.php ++++ b/src/Symfony/Component/HttpClient/MockHttpClient.php +@@ -110,5 +110,5 @@ class MockHttpClient implements HttpClientInterface, ResetInterface + * @return void + */ +- public function reset() ++ public function reset(): void + { + $this->requestsCount = 0; +diff --git a/src/Symfony/Component/HttpClient/ScopingHttpClient.php b/src/Symfony/Component/HttpClient/ScopingHttpClient.php +index a87171d2ca..cec8a6f027 100644 +--- a/src/Symfony/Component/HttpClient/ScopingHttpClient.php ++++ b/src/Symfony/Component/HttpClient/ScopingHttpClient.php +@@ -97,5 +97,5 @@ class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAw + * @return void + */ +- public function reset() ++ public function reset(): void + { + if ($this->client instanceof ResetInterface) { +diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +index d29b1a34e7..1779a080d0 100644 +--- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php ++++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +@@ -362,5 +362,5 @@ class BinaryFileResponse extends Response + * @return void + */ +- public static function trustXSendfileTypeHeader() ++ public static function trustXSendfileTypeHeader(): void + { + self::$trustXSendfileTypeHeader = true; +diff --git a/src/Symfony/Component/HttpFoundation/ExpressionRequestMatcher.php b/src/Symfony/Component/HttpFoundation/ExpressionRequestMatcher.php +index fe65e920d9..6a78e6e779 100644 +--- a/src/Symfony/Component/HttpFoundation/ExpressionRequestMatcher.php ++++ b/src/Symfony/Component/HttpFoundation/ExpressionRequestMatcher.php +@@ -33,5 +33,5 @@ class ExpressionRequestMatcher extends RequestMatcher + * @return void + */ +- public function setExpression(ExpressionLanguage $language, Expression|string $expression) ++ public function setExpression(ExpressionLanguage $language, Expression|string $expression): void + { + $this->language = $language; +diff --git a/src/Symfony/Component/HttpFoundation/FileBag.php b/src/Symfony/Component/HttpFoundation/FileBag.php +index b74a02e2e1..51b4f7f1c3 100644 +--- a/src/Symfony/Component/HttpFoundation/FileBag.php ++++ b/src/Symfony/Component/HttpFoundation/FileBag.php +@@ -35,5 +35,5 @@ class FileBag extends ParameterBag + * @return void + */ +- public function replace(array $files = []) ++ public function replace(array $files = []): void + { + $this->parameters = []; +@@ -44,5 +44,5 @@ class FileBag extends ParameterBag + * @return void + */ +- public function set(string $key, mixed $value) ++ public function set(string $key, mixed $value): void + { + if (!\is_array($value) && !$value instanceof UploadedFile) { +@@ -56,5 +56,5 @@ class FileBag extends ParameterBag + * @return void + */ +- public function add(array $files = []) ++ public function add(array $files = []): void + { + foreach ($files as $key => $file) { +diff --git a/src/Symfony/Component/HttpFoundation/HeaderBag.php b/src/Symfony/Component/HttpFoundation/HeaderBag.php +index 081f26a2d0..7ed43e2c07 100644 +--- a/src/Symfony/Component/HttpFoundation/HeaderBag.php ++++ b/src/Symfony/Component/HttpFoundation/HeaderBag.php +@@ -90,5 +90,5 @@ class HeaderBag implements \IteratorAggregate, \Countable + * @return void + */ +- public function replace(array $headers = []) ++ public function replace(array $headers = []): void + { + $this->headers = []; +@@ -101,5 +101,5 @@ class HeaderBag implements \IteratorAggregate, \Countable + * @return void + */ +- public function add(array $headers) ++ public function add(array $headers): void + { + foreach ($headers as $key => $values) { +@@ -134,5 +134,5 @@ class HeaderBag implements \IteratorAggregate, \Countable + * @return void + */ +- public function set(string $key, string|array|null $values, bool $replace = true) ++ public function set(string $key, string|array|null $values, bool $replace = true): void + { + $key = strtr($key, self::UPPER, self::LOWER); +@@ -180,5 +180,5 @@ class HeaderBag implements \IteratorAggregate, \Countable + * @return void + */ +- public function remove(string $key) ++ public function remove(string $key): void + { + $key = strtr($key, self::UPPER, self::LOWER); +@@ -214,5 +214,5 @@ class HeaderBag implements \IteratorAggregate, \Countable + * @return void + */ +- public function addCacheControlDirective(string $key, bool|string $value = true) ++ public function addCacheControlDirective(string $key, bool|string $value = true): void + { + $this->cacheControl[$key] = $value; +@@ -242,5 +242,5 @@ class HeaderBag implements \IteratorAggregate, \Countable + * @return void + */ +- public function removeCacheControlDirective(string $key) ++ public function removeCacheControlDirective(string $key): void + { + unset($this->cacheControl[$key]); +@@ -270,5 +270,5 @@ class HeaderBag implements \IteratorAggregate, \Countable + * @return string + */ +- protected function getCacheControlHeader() ++ protected function getCacheControlHeader(): string + { + ksort($this->cacheControl); +diff --git a/src/Symfony/Component/HttpFoundation/ParameterBag.php b/src/Symfony/Component/HttpFoundation/ParameterBag.php +index 9d7012de35..545339d4ef 100644 +--- a/src/Symfony/Component/HttpFoundation/ParameterBag.php ++++ b/src/Symfony/Component/HttpFoundation/ParameterBag.php +@@ -64,5 +64,5 @@ class ParameterBag implements \IteratorAggregate, \Countable + * @return void + */ +- public function replace(array $parameters = []) ++ public function replace(array $parameters = []): void + { + $this->parameters = $parameters; +@@ -74,5 +74,5 @@ class ParameterBag implements \IteratorAggregate, \Countable + * @return void + */ +- public function add(array $parameters = []) ++ public function add(array $parameters = []): void + { + $this->parameters = array_replace($this->parameters, $parameters); +@@ -87,5 +87,5 @@ class ParameterBag implements \IteratorAggregate, \Countable + * @return void + */ +- public function set(string $key, mixed $value) ++ public function set(string $key, mixed $value): void + { + $this->parameters[$key] = $value; +@@ -105,5 +105,5 @@ class ParameterBag implements \IteratorAggregate, \Countable + * @return void + */ +- public function remove(string $key) ++ public function remove(string $key): void + { + unset($this->parameters[$key]); +diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php +index 633b4a63e2..b69b8cc723 100644 +--- a/src/Symfony/Component/HttpFoundation/Request.php ++++ b/src/Symfony/Component/HttpFoundation/Request.php +@@ -269,5 +269,5 @@ class Request + * @return void + */ +- public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) ++ public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): void + { + $this->request = new InputBag($request); +@@ -429,5 +429,5 @@ class Request + * @return void + */ +- public static function setFactory(?callable $callable) ++ public static function setFactory(?callable $callable): void + { + self::$requestFactory = $callable; +@@ -535,5 +535,5 @@ class Request + * @return void + */ +- public function overrideGlobals() ++ public function overrideGlobals(): void + { + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&'))); +@@ -577,5 +577,5 @@ class Request + * @return void + */ +- public static function setTrustedProxies(array $proxies, int $trustedHeaderSet) ++ public static function setTrustedProxies(array $proxies, int $trustedHeaderSet): void + { + self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) { +@@ -620,5 +620,5 @@ class Request + * @return void + */ +- public static function setTrustedHosts(array $hostPatterns) ++ public static function setTrustedHosts(array $hostPatterns): void + { + self::$trustedHostPatterns = array_map(fn ($hostPattern) => sprintf('{%s}i', $hostPattern), $hostPatterns); +@@ -668,5 +668,5 @@ class Request + * @return void + */ +- public static function enableHttpMethodParameterOverride() ++ public static function enableHttpMethodParameterOverride(): void + { + self::$httpMethodParameterOverride = true; +@@ -755,5 +755,5 @@ class Request + * @return void + */ +- public function setSession(SessionInterface $session) ++ public function setSession(SessionInterface $session): void + { + $this->session = $session; +@@ -1178,5 +1178,5 @@ class Request + * @return void + */ +- public function setMethod(string $method) ++ public function setMethod(string $method): void + { + $this->method = null; +@@ -1301,5 +1301,5 @@ class Request + * @return void + */ +- public function setFormat(?string $format, string|array $mimeTypes) ++ public function setFormat(?string $format, string|array $mimeTypes): void + { + if (null === static::$formats) { +@@ -1333,5 +1333,5 @@ class Request + * @return void + */ +- public function setRequestFormat(?string $format) ++ public function setRequestFormat(?string $format): void + { + $this->format = $format; +@@ -1365,5 +1365,5 @@ class Request + * @return void + */ +- public function setDefaultLocale(string $locale) ++ public function setDefaultLocale(string $locale): void + { + $this->defaultLocale = $locale; +@@ -1387,5 +1387,5 @@ class Request + * @return void + */ +- public function setLocale(string $locale) ++ public function setLocale(string $locale): void + { + $this->setPhpDefaultLocale($this->locale = $locale); +@@ -1736,5 +1736,5 @@ class Request + * @return string + */ +- protected function prepareRequestUri() ++ protected function prepareRequestUri(): string + { + $requestUri = ''; +@@ -1907,5 +1907,5 @@ class Request + * @return void + */ +- protected static function initializeFormats() ++ protected static function initializeFormats(): void + { + static::$formats = [ +diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher.php +index 8c5f1d8134..fdd3a666e9 100644 +--- a/src/Symfony/Component/HttpFoundation/RequestMatcher.php ++++ b/src/Symfony/Component/HttpFoundation/RequestMatcher.php +@@ -73,5 +73,5 @@ class RequestMatcher implements RequestMatcherInterface + * @return void + */ +- public function matchScheme(string|array|null $scheme) ++ public function matchScheme(string|array|null $scheme): void + { + $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : []; +@@ -83,5 +83,5 @@ class RequestMatcher implements RequestMatcherInterface + * @return void + */ +- public function matchHost(?string $regexp) ++ public function matchHost(?string $regexp): void + { + $this->host = $regexp; +@@ -95,5 +95,5 @@ class RequestMatcher implements RequestMatcherInterface + * @return void + */ +- public function matchPort(?int $port) ++ public function matchPort(?int $port): void + { + $this->port = $port; +@@ -105,5 +105,5 @@ class RequestMatcher implements RequestMatcherInterface + * @return void + */ +- public function matchPath(?string $regexp) ++ public function matchPath(?string $regexp): void + { + $this->path = $regexp; +@@ -117,5 +117,5 @@ class RequestMatcher implements RequestMatcherInterface + * @return void + */ +- public function matchIp(string $ip) ++ public function matchIp(string $ip): void + { + $this->matchIps($ip); +@@ -129,5 +129,5 @@ class RequestMatcher implements RequestMatcherInterface + * @return void + */ +- public function matchIps(string|array|null $ips) ++ public function matchIps(string|array|null $ips): void + { + $ips = null !== $ips ? (array) $ips : []; +@@ -143,5 +143,5 @@ class RequestMatcher implements RequestMatcherInterface + * @return void + */ +- public function matchMethod(string|array|null $method) ++ public function matchMethod(string|array|null $method): void + { + $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : []; +@@ -153,5 +153,5 @@ class RequestMatcher implements RequestMatcherInterface + * @return void + */ +- public function matchAttribute(string $key, string $regexp) ++ public function matchAttribute(string $key, string $regexp): void + { + $this->attributes[$key] = $regexp; +diff --git a/src/Symfony/Component/HttpFoundation/RequestStack.php b/src/Symfony/Component/HttpFoundation/RequestStack.php +index 5aa8ba7934..80742b0764 100644 +--- a/src/Symfony/Component/HttpFoundation/RequestStack.php ++++ b/src/Symfony/Component/HttpFoundation/RequestStack.php +@@ -35,5 +35,5 @@ class RequestStack + * @return void + */ +- public function push(Request $request) ++ public function push(Request $request): void + { + $this->requests[] = $request; +diff --git a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +index 10450ca5e2..3e034007fb 100644 +--- a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php ++++ b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +@@ -59,5 +59,5 @@ class ResponseHeaderBag extends HeaderBag + * @return array + */ +- public function allPreserveCaseWithoutCookies() ++ public function allPreserveCaseWithoutCookies(): array + { + $headers = $this->allPreserveCase(); +@@ -72,5 +72,5 @@ class ResponseHeaderBag extends HeaderBag + * @return void + */ +- public function replace(array $headers = []) ++ public function replace(array $headers = []): void + { + $this->headerNames = []; +@@ -107,5 +107,5 @@ class ResponseHeaderBag extends HeaderBag + * @return void + */ +- public function set(string $key, string|array|null $values, bool $replace = true) ++ public function set(string $key, string|array|null $values, bool $replace = true): void + { + $uniqueKey = strtr($key, self::UPPER, self::LOWER); +@@ -138,5 +138,5 @@ class ResponseHeaderBag extends HeaderBag + * @return void + */ +- public function remove(string $key) ++ public function remove(string $key): void + { + $uniqueKey = strtr($key, self::UPPER, self::LOWER); +@@ -173,5 +173,5 @@ class ResponseHeaderBag extends HeaderBag + * @return void + */ +- public function setCookie(Cookie $cookie) ++ public function setCookie(Cookie $cookie): void + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; +@@ -184,5 +184,5 @@ class ResponseHeaderBag extends HeaderBag + * @return void + */ +- public function removeCookie(string $name, ?string $path = '/', string $domain = null) ++ public function removeCookie(string $name, ?string $path = '/', string $domain = null): void + { + $path ??= '/'; +@@ -237,5 +237,5 @@ class ResponseHeaderBag extends HeaderBag + * @return void + */ +- public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true, string $sameSite = null) ++ public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true, string $sameSite = null): void + { + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite)); +@@ -247,5 +247,5 @@ class ResponseHeaderBag extends HeaderBag + * @return string + */ +- public function makeDisposition(string $disposition, string $filename, string $filenameFallback = '') ++ public function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string + { + return HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback); +diff --git a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php +index ad5a6590a5..cf296c1d6d 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php +@@ -40,5 +40,5 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta + * @return void + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + $this->name = $name; +@@ -48,5 +48,5 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta + * @return void + */ +- public function initialize(array &$attributes) ++ public function initialize(array &$attributes): void + { + $this->attributes = &$attributes; +@@ -71,5 +71,5 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta + * @return void + */ +- public function set(string $name, mixed $value) ++ public function set(string $name, mixed $value): void + { + $this->attributes[$name] = $value; +@@ -84,5 +84,5 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta + * @return void + */ +- public function replace(array $attributes) ++ public function replace(array $attributes): void + { + $this->attributes = []; +diff --git a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php +index e8cd0b5a4d..27eca96974 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php +@@ -36,5 +36,5 @@ interface AttributeBagInterface extends SessionBagInterface + * @return void + */ +- public function set(string $name, mixed $value); ++ public function set(string $name, mixed $value): void; + + /** +@@ -48,5 +48,5 @@ interface AttributeBagInterface extends SessionBagInterface + * @return void + */ +- public function replace(array $attributes); ++ public function replace(array $attributes): void; + + /** +diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php b/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php +index 80bbeda0f8..c4d461cd3a 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php +@@ -39,5 +39,5 @@ class AutoExpireFlashBag implements FlashBagInterface + * @return void + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + $this->name = $name; +@@ -47,5 +47,5 @@ class AutoExpireFlashBag implements FlashBagInterface + * @return void + */ +- public function initialize(array &$flashes) ++ public function initialize(array &$flashes): void + { + $this->flashes = &$flashes; +@@ -61,5 +61,5 @@ class AutoExpireFlashBag implements FlashBagInterface + * @return void + */ +- public function add(string $type, mixed $message) ++ public function add(string $type, mixed $message): void + { + $this->flashes['new'][$type][] = $message; +@@ -103,5 +103,5 @@ class AutoExpireFlashBag implements FlashBagInterface + * @return void + */ +- public function setAll(array $messages) ++ public function setAll(array $messages): void + { + $this->flashes['new'] = $messages; +@@ -111,5 +111,5 @@ class AutoExpireFlashBag implements FlashBagInterface + * @return void + */ +- public function set(string $type, string|array $messages) ++ public function set(string $type, string|array $messages): void + { + $this->flashes['new'][$type] = (array) $messages; +diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php +index 659d59d186..3da6888cec 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php +@@ -39,5 +39,5 @@ class FlashBag implements FlashBagInterface + * @return void + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + $this->name = $name; +@@ -47,5 +47,5 @@ class FlashBag implements FlashBagInterface + * @return void + */ +- public function initialize(array &$flashes) ++ public function initialize(array &$flashes): void + { + $this->flashes = &$flashes; +@@ -55,5 +55,5 @@ class FlashBag implements FlashBagInterface + * @return void + */ +- public function add(string $type, mixed $message) ++ public function add(string $type, mixed $message): void + { + $this->flashes[$type][] = $message; +@@ -94,5 +94,5 @@ class FlashBag implements FlashBagInterface + * @return void + */ +- public function set(string $type, string|array $messages) ++ public function set(string $type, string|array $messages): void + { + $this->flashes[$type] = (array) $messages; +@@ -102,5 +102,5 @@ class FlashBag implements FlashBagInterface + * @return void + */ +- public function setAll(array $messages) ++ public function setAll(array $messages): void + { + $this->flashes = $messages; +diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php +index bbcf7f8b7d..6ef5b35e7d 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php +@@ -26,5 +26,5 @@ interface FlashBagInterface extends SessionBagInterface + * @return void + */ +- public function add(string $type, mixed $message); ++ public function add(string $type, mixed $message): void; + + /** +@@ -33,5 +33,5 @@ interface FlashBagInterface extends SessionBagInterface + * @return void + */ +- public function set(string $type, string|array $messages); ++ public function set(string $type, string|array $messages): void; + + /** +@@ -65,5 +65,5 @@ interface FlashBagInterface extends SessionBagInterface + * @return void + */ +- public function setAll(array $messages); ++ public function setAll(array $messages): void; + + /** +diff --git a/src/Symfony/Component/HttpFoundation/Session/Session.php b/src/Symfony/Component/HttpFoundation/Session/Session.php +index b45be2f8c3..ce73fdb85f 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Session.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Session.php +@@ -73,5 +73,5 @@ class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Cou + * @return void + */ +- public function set(string $name, mixed $value) ++ public function set(string $name, mixed $value): void + { + $this->getAttributeBag()->set($name, $value); +@@ -86,5 +86,5 @@ class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Cou + * @return void + */ +- public function replace(array $attributes) ++ public function replace(array $attributes): void + { + $this->getAttributeBag()->replace($attributes); +@@ -99,5 +99,5 @@ class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Cou + * @return void + */ +- public function clear() ++ public function clear(): void + { + $this->getAttributeBag()->clear(); +@@ -167,5 +167,5 @@ class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Cou + * @return void + */ +- public function save() ++ public function save(): void + { + $this->storage->save(); +@@ -180,5 +180,5 @@ class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Cou + * @return void + */ +- public function setId(string $id) ++ public function setId(string $id): void + { + if ($this->storage->getId() !== $id) { +@@ -195,5 +195,5 @@ class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Cou + * @return void + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + $this->storage->setName($name); +@@ -213,5 +213,5 @@ class Session implements FlashBagAwareSessionInterface, \IteratorAggregate, \Cou + * @return void + */ +- public function registerBag(SessionBagInterface $bag) ++ public function registerBag(SessionBagInterface $bag): void + { + $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex, $this->usageReporter)); +diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php b/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php +index e1c2505549..d88b64acdd 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php ++++ b/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php +@@ -29,5 +29,5 @@ interface SessionBagInterface + * @return void + */ +- public function initialize(array &$array); ++ public function initialize(array &$array): void; + + /** +diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php b/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php +index 534883d2d2..1240e36f74 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php ++++ b/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php +@@ -38,5 +38,5 @@ interface SessionInterface + * @return void + */ +- public function setId(string $id); ++ public function setId(string $id): void; + + /** +@@ -50,5 +50,5 @@ interface SessionInterface + * @return void + */ +- public function setName(string $name); ++ public function setName(string $name): void; + + /** +@@ -86,5 +86,5 @@ interface SessionInterface + * @return void + */ +- public function save(); ++ public function save(): void; + + /** +@@ -103,5 +103,5 @@ interface SessionInterface + * @return void + */ +- public function set(string $name, mixed $value); ++ public function set(string $name, mixed $value): void; + + /** +@@ -115,5 +115,5 @@ interface SessionInterface + * @return void + */ +- public function replace(array $attributes); ++ public function replace(array $attributes): void; + + /** +@@ -129,5 +129,5 @@ interface SessionInterface + * @return void + */ +- public function clear(); ++ public function clear(): void; + + /** +@@ -141,5 +141,5 @@ interface SessionInterface + * @return void + */ +- public function registerBag(SessionBagInterface $bag); ++ public function registerBag(SessionBagInterface $bag): void; + + /** +diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +index 65452a5207..ce0357e36b 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +@@ -242,5 +242,5 @@ class PdoSessionHandler extends AbstractSessionHandler + * @throws \DomainException When an unsupported PDO driver is used + */ +- public function createTable() ++ public function createTable(): void + { + // connect if we are not yet +diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php +index ebe4b748ad..059a6b5ac8 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php +@@ -55,5 +55,5 @@ class MetadataBag implements SessionBagInterface + * @return void + */ +- public function initialize(array &$array) ++ public function initialize(array &$array): void + { + $this->meta = &$array; +@@ -89,5 +89,5 @@ class MetadataBag implements SessionBagInterface + * @return void + */ +- public function stampNew(int $lifetime = null) ++ public function stampNew(int $lifetime = null): void + { + $this->stampCreated($lifetime); +@@ -135,5 +135,5 @@ class MetadataBag implements SessionBagInterface + * @return void + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + $this->name = $name; +diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php +index d30b56d691..0f002902bd 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php +@@ -72,5 +72,5 @@ class MockArraySessionStorage implements SessionStorageInterface + * @return void + */ +- public function setSessionData(array $array) ++ public function setSessionData(array $array): void + { + $this->data = $array; +@@ -112,5 +112,5 @@ class MockArraySes 67ED sionStorage implements SessionStorageInterface + * @return void + */ +- public function setId(string $id) ++ public function setId(string $id): void + { + if ($this->started) { +@@ -129,5 +129,5 @@ class MockArraySessionStorage implements SessionStorageInterface + * @return void + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + $this->name = $name; +@@ -137,5 +137,5 @@ class MockArraySessionStorage implements SessionStorageInterface + * @return void + */ +- public function save() ++ public function save(): void + { + if (!$this->started || $this->closed) { +@@ -150,5 +150,5 @@ class MockArraySessionStorage implements SessionStorageInterface + * @return void + */ +- public function clear() ++ public function clear(): void + { + // clear out the bags +@@ -167,5 +167,5 @@ class MockArraySessionStorage implements SessionStorageInterface + * @return void + */ +- public function registerBag(SessionBagInterface $bag) ++ public function registerBag(SessionBagInterface $bag): void + { + $this->bags[$bag->getName()] = $bag; +@@ -193,5 +193,5 @@ class MockArraySessionStorage implements SessionStorageInterface + * @return void + */ +- public function setMetadataBag(MetadataBag $bag = null) ++ public function setMetadataBag(MetadataBag $bag = null): void + { + if (1 > \func_num_args()) { +@@ -223,5 +223,5 @@ class MockArraySessionStorage implements SessionStorageInterface + * @return void + */ +- protected function loadSession() ++ protected function loadSession(): void + { + $bags = array_merge($this->bags, [$this->metadataBag]); +diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php +index 95f69f2e13..971b890f0f 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php +@@ -77,5 +77,5 @@ class MockFileSessionStorage extends MockArraySessionStorage + * @return void + */ +- public function save() ++ public function save(): void + { + if (!$this->started) { +diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +index 7c6b6f9296..0b63abb810 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +@@ -187,5 +187,5 @@ class NativeSessionStorage implements SessionStorageInterface + * @return void + */ +- public function setId(string $id) ++ public function setId(string $id): void + { + $this->saveHandler->setId($id); +@@ -200,5 +200,5 @@ class NativeSessionStorage implements SessionStorageInterface + * @return void + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + $this->saveHandler->setName($name); +@@ -232,5 +232,5 @@ class NativeSessionStorage implements SessionStorageInterface + * @return void + */ +- public function save() ++ public function save(): void + { + // Store a copy so we can restore the bags in case the session was not left empty +@@ -274,5 +274,5 @@ class NativeSessionStorage implements SessionStorageInterface + * @return void + */ +- public function clear() ++ public function clear(): void + { + // clear out the bags +@@ -291,5 +291,5 @@ class NativeSessionStorage implements SessionStorageInterface + * @return void + */ +- public function registerBag(SessionBagInterface $bag) ++ public function registerBag(SessionBagInterface $bag): void + { + if ($this->started) { +@@ -318,5 +318,5 @@ class NativeSessionStorage implements SessionStorageInterface + * @return void + */ +- public function setMetadataBag(MetadataBag $metaBag = null) ++ public function setMetadataBag(MetadataBag $metaBag = null): void + { + if (1 > \func_num_args()) { +@@ -351,5 +351,5 @@ class NativeSessionStorage implements SessionStorageInterface + * @return void + */ +- public function setOptions(array $options) ++ public function setOptions(array $options): void + { + if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) { +@@ -397,5 +397,5 @@ class NativeSessionStorage implements SessionStorageInterface + * @throws \InvalidArgumentException + */ +- public function setSaveHandler(AbstractProxy|\SessionHandlerInterface $saveHandler = null) ++ public function setSaveHandler(AbstractProxy|\SessionHandlerInterface $saveHandler = null): void + { + if (1 > \func_num_args()) { +@@ -430,5 +430,5 @@ class NativeSessionStorage implements SessionStorageInterface + * @return void + */ +- protected function loadSession(array &$session = null) ++ protected function loadSession(array &$session = null): void + { + if (null === $session) { +diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php +index 28cb3c3d05..fc5452c5b0 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php +@@ -45,5 +45,5 @@ class PhpBridgeSessionStorage extends NativeSessionStorage + * @return void + */ +- public function clear() ++ public function clear(): void + { + // clear out the bags and nothing else that may be set +diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php +index 2fcd06b10b..4981f75360 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php +@@ -76,5 +76,5 @@ abstract class AbstractProxy + * @throws \LogicException + */ +- public function setId(string $id) ++ public function setId(string $id): void + { + if ($this->isActive()) { +@@ -100,5 +100,5 @@ abstract class AbstractProxy + * @throws \LogicException + */ +- public function setName(string $name) ++ public function setName(string $name): void + { + if ($this->isActive()) { +diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php +index ed2189e4e7..28e90cdcf9 100644 +--- a/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php ++++ b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php +@@ -44,5 +44,5 @@ interface SessionStorageInterface + * @return void + */ +- public function setId(string $id); ++ public function setId(string $id): void; + + /** +@@ -56,5 +56,5 @@ interface SessionStorageInterface + * @return void + */ +- public function setName(string $name); ++ public function setName(string $name): void; + + /** +@@ -100,5 +100,5 @@ interface SessionStorageInterface + * is already closed + */ +- public function save(); ++ public function save(): void; + + /** +@@ -107,5 +107,5 @@ interface SessionStorageInterface + * @return void + */ +- public function clear(); ++ public function clear(): void; + + /** +@@ -121,5 +121,5 @@ interface SessionStorageInterface + * @return void + */ +- public function registerBag(SessionBagInterface $bag); ++ public function registerBag(SessionBagInterface $bag): void; + + public function getMetadataBag(): MetadataBag; +diff --git a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php +index 2ddf55f2cb..52049a92b4 100644 +--- a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php ++++ b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php +@@ -35,5 +35,5 @@ abstract class Bundle implements BundleInterface + * @return void + */ +- public function boot() ++ public function boot(): void + { + } +@@ -42,5 +42,5 @@ abstract class Bundle implements BundleInterface + * @return void + */ +- public function shutdown() ++ public function shutdown(): void + { + } +@@ -52,5 +52,5 @@ abstract class Bundle implements BundleInterface + * @return void + */ +- public function build(ContainerBuilder $container) ++ public function build(ContainerBuilder $container): void + { + } +@@ -122,5 +122,5 @@ abstract class Bundle implements BundleInterface + * @return void + */ +- public function registerCommands(Application $application) ++ public function registerCommands(Application $application): void + { + } +diff --git a/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php b/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php +index 02cb9641db..abe408eb24 100644 +--- a/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php ++++ b/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php +@@ -28,5 +28,5 @@ interface BundleInterface extends ContainerAwareInterface + * @return void + */ +- public function boot(); ++ public function boot(): void; + + /** +@@ -35,5 +35,5 @@ interface BundleInterface extends ContainerAwareInterface + * @return void + */ +- public function shutdown(); ++ public function shutdown(): void; + + /** +@@ -44,5 +44,5 @@ interface BundleInterface extends ContainerAwareInterface + * @return void + */ +- public function build(ContainerBuilder $container); ++ public function build(ContainerBuilder $container): void; + + /** +diff --git a/src/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php b/src/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php +index 5ca4265624..1cb3611f8d 100644 +--- a/src/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php ++++ b/src/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php +@@ -24,4 +24,4 @@ interface CacheClearerInterface + * @return void + */ +- public function clear(string $cacheDir); ++ public function clear(string $cacheDir): void; + } +diff --git a/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php b/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php +index 3c99b74af3..fdb68feb2c 100644 +--- a/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php ++++ b/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php +@@ -61,5 +61,5 @@ class Psr6CacheClearer implements CacheClearerInterface + * @return void + */ +- public function clear(string $cacheDir) ++ public function clear(string $cacheDir): void + { + foreach ($this->pools as $pool) { +diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php +index f940ba4a72..5178dc96bb 100644 +--- a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php ++++ b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php +@@ -22,5 +22,5 @@ abstract class CacheWarmer implements CacheWarmerInterface + * @return void + */ +- protected function writeCacheFile(string $file, $content) ++ protected function writeCacheFile(string $file, $content): void + { + $tmpFile = @tempnam(\dirname($file), basename($file)); +diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php +index 1f1740b7e2..22dc8ea26a 100644 +--- a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php ++++ b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php +@@ -29,4 +29,4 @@ interface CacheWarmerInterface extends WarmableInterface + * @return bool + */ +- public function isOptional(); ++ public function isOptional(): bool; + } +diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php b/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php +index 2f442cb536..d98909cfae 100644 +--- a/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php ++++ b/src/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php +@@ -24,4 +24,4 @@ interface WarmableInterface + * @return string[] A list of classes or files to preload on PHP 7.4+ + */ +- public function warmUp(string $cacheDir); ++ public function warmUp(string $cacheDir): array; + } +diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php +index 698ed31397..c5a3eea409 100644 +--- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php ++++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php +@@ -59,5 +59,5 @@ abstract class DataCollector implements DataCollectorInterface + * @return callable[] The casters to add to the cloner + */ +- protected function getCasters() ++ protected function getCasters(): array + { + $casters = [ +diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php +index 8df94ccb8f..42288d8a02 100644 +--- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php ++++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php +@@ -28,5 +28,5 @@ interface DataCollectorInterface extends ResetInterface + * @return void + */ +- public function collect(Request $request, Response $response, \Throwable $exception = null); ++ public function collect(Request $request, Response $response, \Throwable $exception = null): void; + + /** +@@ -35,4 +35,4 @@ interface DataCollectorInterface extends ResetInterface + * @return string + */ +- public function getName(); ++ public function getName(): string; + } +diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LateDataCollectorInterface.php b/src/Symfony/Component/HttpKernel/DataCollector/LateDataCollectorInterface.php +index efa1a4f737..752eb19faf 100644 +--- a/src/Symfony/Component/HttpKernel/DataCollector/LateDataCollectorInterface.php ++++ b/src/Symfony/Component/HttpKernel/DataCollector/LateDataCollectorInterface.php +@@ -24,4 +24,4 @@ interface LateDataCollectorInterface + * @return void + */ +- public function lateCollect(); ++ public function lateCollect(): void; + } +diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +index 094683ccce..5582af522e 100644 +--- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php ++++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +@@ -199,5 +199,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return ParameterBag + */ +- public function getRequestRequest() ++ public function getRequestRequest(): ParameterBag + { + return new ParameterBag($this->data['request_request']->getValue()); +@@ -207,5 +207,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return ParameterBag + */ +- public function getRequestQuery() ++ public function getRequestQuery(): ParameterBag + { + return new ParameterBag($this->data['request_query']->getValue()); +@@ -215,5 +215,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return ParameterBag + */ +- public function getRequestFiles() ++ public function getRequestFiles(): ParameterBag + { + return new ParameterBag($this->data['request_files']->getValue()); +@@ -223,5 +223,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return ParameterBag + */ +- public function getRequestHeaders() ++ public function getRequestHeaders(): ParameterBag + { + return new ParameterBag($this->data['request_headers']->getValue()); +@@ -231,5 +231,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return ParameterBag + */ +- public function getRequestServer(bool $raw = false) ++ public function getRequestServer(bool $raw = false): ParameterBag + { + return new ParameterBag($this->data['request_server']->getValue($raw)); +@@ -239,5 +239,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return ParameterBag + */ +- public function getRequestCookies(bool $raw = false) ++ public function getRequestCookies(bool $raw = false): ParameterBag + { + return new ParameterBag($this->data['request_cookies']->getValue($raw)); +@@ -247,5 +247,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return ParameterBag + */ +- public function getRequestAttributes() ++ public function getRequestAttributes(): ParameterBag + { + return new ParameterBag($this->data['request_attributes']->getValue()); +@@ -255,5 +255,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return ParameterBag + */ +- public function getResponseHeaders() ++ public function getResponseHeaders(): ParameterBag + { + return new ParameterBag($this->data['response_headers']->getValue()); +@@ -263,5 +263,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return ParameterBag + */ +- public function getResponseCookies() ++ public function getResponseCookies(): ParameterBag + { + return new ParameterBag($this->data['response_cookies']->getValue()); +@@ -301,5 +301,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return bool + */ +- public function isJsonRequest() ++ public function isJsonRequest(): bool + { + return 1 === preg_match('{^application/(?:\w+\++)*json$}i', $this->data['request_headers']['content-type']); +@@ -309,5 +309,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return string|null + */ +- public function getPrettyJson() ++ public function getPrettyJson(): ?string + { + $decoded = json_decode($this->getContent()); +@@ -344,5 +344,5 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter + * @return ParameterBag + */ +- public function getDotenvVars() ++ public function getDotenvVars(): ParameterBag + { + return new ParameterBag($this->data['dotenv_vars']->getValue()); +diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php +index 444138da70..42a3c28b5f 100644 +--- a/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php ++++ b/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php +@@ -52,5 +52,5 @@ class RouterDataCollector extends DataCollector + * @return void + */ +- public function reset() ++ public function reset(): void + { + $this->controllers = new \SplObjectStorage(); +@@ -66,5 +66,5 @@ class RouterDataCollector extends DataCollector + * @return string + */ +- protected function guessRoute(Request $request, string|object|array $controller) ++ protected function guessRoute(Request $request, string|object|array $controller): string + { + return 'n/a'; +@@ -76,5 +76,5 @@ class RouterDataCollector extends DataCollector + * @return void + */ +- public function onKernelController(ControllerEvent $event) ++ public function onKernelController(ControllerEvent $event): void + { + $this->controllers[$event->getRequest()] = $event->getController(); +diff --git a/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php b/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php +index fcb100859f..8bf3ef09d5 100644 +--- a/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php ++++ b/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php +@@ -53,5 +53,5 @@ class FileLinkFormatter + * @return string|false + */ +- public function format(string $file, int $line): string|bool ++ public function format(string $file, int $line): string|false + { + if ($fmt = $this->getFileLinkFormat()) { +diff --git a/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php +index 4f6c34bc74..c84198c912 100644 +--- a/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php ++++ b/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php +@@ -27,5 +27,5 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher + * @return void + */ +- protected function beforeDispatch(string $eventName, object $event) ++ protected function beforeDispatch(string $eventName, object $event): void + { + switch ($eventName) { +@@ -62,5 +62,5 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher + * @return void + */ +- protected function afterDispatch(string $eventName, object $event) ++ protected function afterDispatch(string $eventName, object $event): void + { + switch ($eventName) { +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php +index 1924b1ddb0..62c58c8e8b 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php +@@ -35,5 +35,5 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $annotatedClasses = []; +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php +index 12d468cf04..2ca89c128e 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php +@@ -38,4 +38,4 @@ abstract class ConfigurableExtension extends Extension + * @return void + */ +- abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container); ++ abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void; + } +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php +index dff3e248ae..381db9aa8f 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php +@@ -34,5 +34,5 @@ class ControllerArgumentValueResolverPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('argument_resolver')) { +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php b/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php +index d72efa1724..ea38e4d301 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php +@@ -38,5 +38,5 @@ abstract class Extension extends BaseExtension + * @return void + */ +- public function addAnnotatedClassesToCompile(array $annotatedClasses) ++ public function addAnnotatedClassesToCompile(array $annotatedClasses): void + { + $this->annotatedClasses = array_merge($this->annotatedClasses, $annotatedClasses); +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php +index f41d58b81b..cb2b5c484c 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php +@@ -29,5 +29,5 @@ class FragmentRendererPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('fragment.handler')) { +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/LoggerPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/LoggerPass.php +index 2b6cb00793..6bf9e49796 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/LoggerPass.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/LoggerPass.php +@@ -29,5 +29,5 @@ class LoggerPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $container->setAlias(LoggerInterface::class, 'logger') +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionC A3DB onfigurationPass.php +index cec23e1970..946d9f6802 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php +@@ -35,5 +35,5 @@ class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPas + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + foreach ($this->extensions as $extension) { +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +index d0e05340d8..18b5beb201 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +@@ -37,5 +37,5 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('argument_resolver.service') && !$container->hasDefinition('argument_resolver.not_tagged_controller')) { +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php +index 2a01365bd3..d321073903 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php +@@ -27,5 +27,5 @@ class RegisterLocaleAwareServicesPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('locale_aware_listener')) { +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php +index 7a21fe0e59..1a87f4f7e5 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php +@@ -25,5 +25,5 @@ class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + $controllerLocator = $container->findDefinition('argument_resolver.controller_locator'); +diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php +index da9f8d6320..cfef739f87 100644 +--- a/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php ++++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php +@@ -27,5 +27,5 @@ class ResettableServicePass implements CompilerPassInterface + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + if (!$container->has('services_resetter')) { +diff --git a/src/Symfony/Component/HttpKernel/Event/RequestEvent.php b/src/Symfony/Component/HttpKernel/Event/RequestEvent.php +index b81a79b780..e05ed715e2 100644 +--- a/src/Symfony/Component/HttpKernel/Event/RequestEvent.php ++++ b/src/Symfony/Component/HttpKernel/Event/RequestEvent.php +@@ -40,5 +40,5 @@ class RequestEvent extends KernelEvent + * @return void + */ +- public function setResponse(Response $response) ++ public function setResponse(Response $response): void + { + $this->response = $response; +diff --git a/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php b/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php +index 723e758cd0..f3a5618e3d 100644 +--- a/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php ++++ b/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php +@@ -50,5 +50,5 @@ class CacheAttributeListener implements EventSubscriberInterface + * @return void + */ +- public function onKernelControllerArguments(ControllerArgumentsEvent $event) ++ public function onKernelControllerArguments(ControllerArgumentsEvent $event): void + { + $request = $event->getRequest(); +@@ -96,5 +96,5 @@ class CacheAttributeListener implements EventSubscriberInterface + * @return void + */ +- public function onKernelResponse(ResponseEvent $event) ++ public function onKernelResponse(ResponseEvent $event): void + { + $request = $event->getRequest(); +diff --git a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php +index 4a40909157..61edeb266c 100644 +--- a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php ++++ b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php +@@ -40,5 +40,5 @@ class DumpListener implements EventSubscriberInterface + * @return void + */ +- public function configure() ++ public function configure(): void + { + $cloner = $this->cloner; +diff --git a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php +index cc6936cfda..c4a74bdd93 100644 +--- a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php ++++ b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php +@@ -55,5 +55,5 @@ class ErrorListener implements EventSubscriberInterface + * @return void + */ +- public function logKernelException(ExceptionEvent $event) ++ public function logKernelException(ExceptionEvent $event): void + { + $throwable = $event->getThrowable(); +@@ -96,5 +96,5 @@ class ErrorListener implements EventSubscriberInterface + * @return void + */ +- public function onKernelException(ExceptionEvent $event) ++ public function onKernelException(ExceptionEvent $event): void + { + if (null === $this->controller) { +@@ -142,5 +142,5 @@ class ErrorListener implements EventSubscriberInterface + * @return void + */ +- public function onControllerArguments(ControllerArgumentsEvent $event) ++ public function onControllerArguments(ControllerArgumentsEvent $event): void + { + $e = $event->getRequest()->attributes->get('exception'); +diff --git a/src/Symfony/Component/HttpKernel/Exception/HttpException.php b/src/Symfony/Component/HttpKernel/Exception/HttpException.php +index e12abce004..3354a5aebf 100644 +--- a/src/Symfony/Component/HttpKernel/Exception/HttpException.php ++++ b/src/Symfony/Component/HttpKernel/Exception/HttpException.php +@@ -43,5 +43,5 @@ class HttpException extends \RuntimeException implements HttpExceptionInterface + * @return void + */ +- public function setHeaders(array $headers) ++ public function setHeaders(array $headers): void + { + $this->headers = $headers; +diff --git a/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php b/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php +index 62b21e6d4e..67984f7e75 100644 +--- a/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php ++++ b/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php +@@ -52,5 +52,5 @@ class FragmentHandler + * @return void + */ +- public function addRenderer(FragmentRendererInterface $renderer) ++ public function addRenderer(FragmentRendererInterface $renderer): void + { + $this->renderers[$renderer->getName()] = $renderer; +diff --git a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php +index 3650d1700d..055b57fc77 100644 +--- a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php ++++ b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php +@@ -107,5 +107,5 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer + * @return Request + */ +- protected function createSubRequest(string $uri, Request $request) ++ protected function createSubRequest(string $uri, Request $request): Request + { + $cookies = $request->cookies->all(); +diff --git a/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php +index 47027233a7..2be5cfc7de 100644 +--- a/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php ++++ b/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php +@@ -35,5 +35,5 @@ abstract class RoutableFragmentRenderer implements FragmentRendererInterface + * @return void + */ +- public function setFragmentPath(string $path) ++ public function setFragmentPath(string $path): void + { + $this->fragmentPath = $path; +diff --git a/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php b/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php +index ce34979bbd..27d49f6a8c 100644 +--- a/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php ++++ b/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php +@@ -63,5 +63,5 @@ abstract class AbstractSurrogate implements SurrogateInterface + * @return void + */ +- public function addSurrogateCapability(Request $request) ++ public function addSurrogateCapability(Request $request): void + { + $current = $request->headers->get('Surrogate-Capability'); +@@ -112,5 +112,5 @@ abstract class AbstractSurrogate implements SurrogateInterface + * @return void + */ +- protected function removeFromControl(Response $response) ++ protected function removeFromControl(Response $response): void + { + if (!$response->headers->has('Surrogate-Control')) { +diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php +index d1761ccaae..dfdf0f4e7c 100644 +--- a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php ++++ b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php +@@ -36,5 +36,5 @@ class Esi extends AbstractSurrogate + * @return void + */ +- public function addSurrogateControl(Response $response) ++ public function addSurrogateControl(Response $response): void + { + if (str_contains($response->getContent(), 'surrogate?->addSurrogateCapability($request); +@@ -598,5 +598,5 @@ class HttpCache implements HttpKernelInterface, TerminableInterface + * @throws \Exception + */ +- protected function store(Request $request, Response $response) ++ protected function store(Request $request, Response $response): void + { + try { +@@ -680,5 +680,5 @@ class HttpCache implements HttpKernelInterface, TerminableInterface + * @return void + */ +- protected function processResponseBody(Request $request, Response $response) ++ protected function processResponseBody(Request $request, Response $response): void + { + if ($this->surrogate?->needsParsing($response)) { +diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php +index 1bdaab148a..7976d00605 100644 +--- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php ++++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php +@@ -58,5 +58,5 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface + * @return void + */ +- public function add(Response $response) ++ public function add(Response $response): void + { + ++$this->embeddedResponses; +@@ -102,5 +102,5 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface + * @return void + */ +- public function update(Response $response) ++ public function update(Response $response): void + { + // if we have no embedded Response, do nothing +diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php +index 33c8bd9412..8fe6df2512 100644 +--- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php ++++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php +@@ -31,5 +31,5 @@ interface ResponseCacheStrategyInterface + * @return void + */ +- public function add(Response $response); ++ public function add(Response $response): void; + + /** +@@ -38,4 +38,4 @@ interface ResponseCacheStrategyInterface + * @return void + */ +- public function update(Response $response); ++ public function update(Response $response): void; + } +diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php +index 45a9555264..f969a3684a 100644 +--- a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php ++++ b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php +@@ -30,5 +30,5 @@ class Ssi extends AbstractSurrogate + * @return void + */ +- public function addSurrogateControl(Response $response) ++ public function addSurrogateControl(Response $response): void + { + if (str_contains($response->getContent(), ' + + + + + + ``` + + After: + ```xml + + + + + ``` + +FrameworkBundle +--------------- + + * Deprecate the `notifier.logger_notification_listener` service, use the `notifier.notification_logger_listener` service instead + * Deprecate the `Http\Client\HttpClient` service, use `Psr\Http\Client\ClientInterface` instead + +HttpClient +---------- + + * The default user agents have been renamed from `Symfony HttpClient/Amp`, `Symfony HttpClient/Curl` + and `Symfony HttpClient/Native` to `Symfony HttpClient (Amp)`, `Symfony HttpClient (Curl)` + and `Symfony HttpClient (Native)` respectively to comply with the RFC 9110 specification + +HttpFoundation +-------------- + + * `Response::sendHeaders()` now takes an optional `$statusCode` parameter + +HttpFoundation +-------------- + + * Deprecate conversion of invalid values in `ParameterBag::getInt()` and `ParameterBag::getBoolean()` + * Deprecate ignoring invalid values when using `ParameterBag::filter()`, unless flag `FILTER_NULL_ON_FAILURE` is set + +HttpKernel +---------- + + * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead + +Lock +---- + + * Deprecate the `gcProbablity` option to fix a typo in its name, use the `gcProbability` option instead + +Messenger +--------- + + * Deprecate `Symfony\Component\Messenger\Transport\InMemoryTransport` and + `Symfony\Component\Messenger\Transport\InMemoryTransportFactory` in favor of + `Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport` and + `Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory` + * Deprecate `StopWorkerOnSigtermSignalListener` in favor of `StopWorkerOnSignalsListener` + +Notifier +-------- + + * [BC BREAK] The following data providers for `TransportTestCase` are now static: `toStringProvider()`, `supportedMessagesProvider()` and `unsupportedMessagesProvider()` + * [BC BREAK] The `TransportTestCase::createTransport()` method is now static + +Security +-------- + + * Deprecate passing a secret as the 2nd argument to the constructor of `Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler` + +SecurityBundle +-------------- + + * Deprecate enabling bundle and not configuring it + * Deprecate the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead + +Validator +--------- + + * Implementing the `ConstraintViolationInterface` without implementing the `getConstraint()` method is deprecated + +Serializer +---------- + + * Deprecate `CacheableSupportsMethodInterface` in favor of the new `getSupportedTypes(?string $format)` methods + * The following Normalizer classes will become final in 7.0: + * `ConstraintViolationListNormalizer` + * `CustomNormalizer` + * `DataUriNormalizer` + * `DateIntervalNormalizer` + * `DateTimeNormalizer` + * `DateTimeZoneNormalizer` + * `GetSetMethodNormalizer` + * `JsonSerializableNormalizer` + * `ObjectNormalizer` + * `PropertyNormalizer` diff --git a/composer.json b/composer.json index 5c338d388cfd0..3f1cfb489f294 100644 --- a/composer.json +++ b/composer.json @@ -53,10 +53,12 @@ "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php83": "^1.27", "symfony/polyfill-uuid": "^1.15" }, "replace": { "symfony/asset": "self.version", + "symfony/asset-mapper": "self.version", "symfony/browser-kit": "self.version", "symfony/cache": "self.version", "symfony/clock": "self.version", @@ -93,7 +95,9 @@ "symfony/property-info": "self.version", "symfony/proxy-manager-bridge": "self.version", "symfony/rate-limiter": "self.version", + "symfony/remote-event": "self.version", "symfony/routing": "self.version", + "symfony/scheduler": "self.version", "symfony/security-bundle": "self.version", "symfony/security-core": "self.version", "symfony/security-csrf": "self.version", @@ -112,6 +116,7 @@ "symfony/var-exporter": "self.version", "symfony/web-link": "self.version", "symfony/web-profiler-bundle": "self.version", + "symfony/webhook": "self.version", "symfony/workflow": "self.version", "symfony/yaml": "self.version" }, @@ -127,7 +132,8 @@ "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.7.4", + "doctrine/orm": "^2.12", + "dragonmantank/cron-expression": "^3", "egulias/email-validator": "^2.1.10|^3.1|^4", "guzzlehttp/promises": "^1.4", "league/html-to-markdown": "^5.0", @@ -138,7 +144,7 @@ "php-http/httplug": "^1.0|^2.0", "phpdocumentor/reflection-docblock": "^5.2", "phpstan/phpdoc-parser": "^1.0", - "predis/predis": "~1.1", + "predis/predis": "^1.1|^2.0", "psr/http-client": "^1.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "symfony/mercure-bundle": "^0.3", @@ -147,7 +153,9 @@ "symfony/security-acl": "~2.8|~3.0", "twig/cssinliner-extra": "^2.12|^3", "twig/inky-extra": "^2.12|^3", - "twig/markdown-extra": "^2.12|^3" + "twig/markdown-extra": "^2.12|^3", + "web-token/jwt-checker": "^3.1", + "web-token/jwt-signature-algorithm-ecdsa": "^3.1" }, "conflict": { "ext-psr": "<1.1|>=2", @@ -186,6 +194,7 @@ }, "autoload-dev": { "files": [ + "src/Symfony/Component/Clock/Resources/now.php", "src/Symfony/Component/VarDumper/Resources/functions/dump.php" ] }, @@ -195,7 +204,7 @@ "url": "src/Symfony/Contracts", "options": { "versions": { - "symfony/contracts": "3.2.x-dev" + "symfony/contracts": "3.3.x-dev" } } }, diff --git a/psalm.xml b/psalm.xml index 7a895615e7457..e6cfa6009e135 100644 --- a/psalm.xml +++ b/psalm.xml @@ -26,6 +26,8 @@ + + diff --git a/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php b/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php index 74caf14c9af55..529bf05dc7767 100644 --- a/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php +++ b/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php @@ -11,11 +11,14 @@ namespace Symfony\Bridge\Doctrine\Attribute; +use Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver; +use Symfony\Component\HttpKernel\Attribute\ValueResolver; + /** * Indicates that a controller argument should receive an Entity. */ #[\Attribute(\Attribute::TARGET_PARAMETER)] -class MapEntity +class MapEntity extends ValueResolver { public function __construct( public ?string $class = null, @@ -26,8 +29,10 @@ public function __construct( public ?bool $stripNull = null, public array|string|null $id = null, public ?bool $evictCache = null, - public bool $disabled = false, + bool $disabled = false, + string $resolver = EntityValueResolver::class, ) { + parent::__construct($resolver, $disabled); } public function withDefaults(self $defaults, ?string $class): static diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index b313594e4ec6a..93d7ac92b4ce1 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,15 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate passing Doctrine subscribers to `ContainerAwareEventManager` class, use listeners instead + * Add `AbstractSchemaListener`, `LockStoreSchemaListener` and `PdoSessionHandlerSchemaListener` + * Deprecate `DoctrineDbalCacheAdapterSchemaSubscriber` in favor of `DoctrineDbalCacheAdapterSchemaListener` + * Deprecate `MessengerTransportDoctrineSchemaSubscriber` in favor of `MessengerTransportDoctrineSchemaListener` + * Deprecate `RememberMeTokenProviderDoctrineSchemaSubscriber` in favor of `RememberMeTokenProviderDoctrineSchemaListener` + 6.2 --- diff --git a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php index bf337bf751635..42cb71bfca07c 100644 --- a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php +++ b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php @@ -29,19 +29,18 @@ class ContainerAwareEventManager extends EventManager * => */ private array $listeners = []; - private array $subscribers; private array $initialized = []; private bool $initializedSubscribers = false; private array $methods = []; private ContainerInterface $container; /** - * @param list $subscriberIds List of subscribers, subscriber ids, or [events, listener] tuples + * @param list $listeners List of [events, listener] tuples */ - public function __construct(ContainerInterface $container, array $subscriberIds = []) + public function __construct(ContainerInterface $container, array $listeners = []) { $this->container = $container; - $this->subscribers = $subscriberIds; + $this->listeners = $listeners; } public function dispatchEvent($eventName, EventArgs $eventArgs = null): void @@ -167,7 +166,7 @@ public function removeEventSubscriber(EventSubscriber $subscriber): void parent::removeEventSubscriber($subscriber); } - private function initializeListeners(string $eventName) + private function initializeListeners(string $eventName): void { $this->initialized[$eventName] = true; foreach ($this->listeners[$eventName] as $hash => $listener) { @@ -179,20 +178,23 @@ private function initializeListeners(string $eventName) } } - private function initializeSubscribers() + private function initializeSubscribers(): void { $this->initializedSubscribers = true; - foreach ($this->subscribers as $subscriber) { - if (\is_array($subscriber)) { - $this->addEventListener(...$subscriber); + $listeners = $this->listeners; + $this->listeners = []; + foreach ($listeners as $listener) { + if (\is_array($listener)) { + $this->addEventListener(...$listener); continue; } - if (\is_string($subscriber)) { - $subscriber = $this->container->get($subscriber); + if (\is_string($listener)) { + $listener = $this->container->get($listener); } - parent::addEventSubscriber($subscriber); + // throw new \InvalidArgumentException(sprintf('Using Doctrine subscriber "%s" is not allowed, declare it as a listener instead.', \is_object($listener) ? $listener::class : $listener)); + trigger_deprecation('symfony/doctrine-bridge', '6.3', 'Using Doctrine subscribers as services is deprecated, declare listeners instead'); + parent::addEventSubscriber($listener); } - $this->subscribers = []; } private function getHash(string|object $listener): string diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index f0f2b65ac3f15..ada5fcbd49804 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -47,12 +47,17 @@ public function __construct( /** * Adds the stack logger for a connection. + * + * @return void */ public function addLogger(string $name, DebugStack $logger) { $this->loggers[$name] = $logger; } + /** + * @return void + */ public function collect(Request $request, Response $response, \Throwable $exception = null) { $this->data = [ @@ -81,6 +86,9 @@ private function collectQueries(): array return $queries; } + /** + * @return void + */ public function reset() { $this->data = []; @@ -107,6 +115,9 @@ public function getConnections() return $this->data['connections']; } + /** + * @return int + */ public function getQueryCount() { return array_sum(array_map('count', $this->data['queries'])); diff --git a/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php b/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php index 95dc8825ed213..4fa5057fe29fe 100644 --- a/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php +++ b/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php @@ -32,6 +32,9 @@ public function __construct(ContainerInterface $container) $this->container = $container; } + /** + * @return void + */ public function addFixture(FixtureInterface $fixture) { if ($fixture instanceof ContainerAwareInterface) { diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index cd5fc91cb32b7..1ce0ffd40cd9b 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -38,6 +38,8 @@ abstract class AbstractDoctrineExtension extends Extension /** * @param array $objectManager A configured object manager * + * @return void + * * @throws \InvalidArgumentException */ protected function loadMappingInformation(array $objectManager, ContainerBuilder $container) @@ -105,6 +107,8 @@ protected function loadMappingInformation(array $objectManager, ContainerBuilder * Register the alias for this mapping driver. * * Aliases can be used in the Query languages of all the Doctrine object managers to simplify writing tasks. + * + * @return void */ protected function setMappingDriverAlias(array $mappingConfig, string $mappingName) { @@ -118,6 +122,8 @@ protected function setMappingDriverAlias(array $mappingConfig, string $mappingNa /** * Register the mapping driver configuration for later use with the object managers metadata driver chain. * + * @return void + * * @throws \InvalidArgumentException */ protected function setMappingDriverConfig(array $mappingConfig, string $mappingName) @@ -172,6 +178,8 @@ protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \Re /** * Register all the collected mapping information with the object manager by registering the appropriate mapping drivers. + * + * @return void */ protected function registerMappingDrivers(array $objectManager, ContainerBuilder $container) { @@ -227,6 +235,8 @@ protected function registerMappingDrivers(array $objectManager, ContainerBuilder /** * Assertion if the specified mapping information is valid. * + * @return void + * * @throws \InvalidArgumentException */ protected function assertValidMappingConfiguration(array $mappingConfig, string $objectManagerName) @@ -295,14 +305,14 @@ private function detectMappingType(string $directory, ContainerBuilder $containe $content = file_get_contents($file); if ( - preg_match('/^#\[.*'.$quotedMappingObjectName.'\b/m', $content) || - preg_match('/^#\[.*Embeddable\b/m', $content) + preg_match('/^#\[.*'.$quotedMappingObjectName.'\b/m', $content) + || preg_match('/^#\[.*Embeddable\b/m', $content) ) { break; } if ( - preg_match('/^(?: \*|\/\*\*) @.*'.$quotedMappingObjectName.'\b/m', $content) || - preg_match('/^(?: \*|\/\*\*) @.*Embeddable\b/m', $content) + preg_match('/^(?: \*|\/\*\*) @.*'.$quotedMappingObjectName.'\b/m', $content) + || preg_match('/^(?: \*|\/\*\*) @.*Embeddable\b/m', $content) ) { $type = 'annotation'; break; @@ -315,6 +325,8 @@ private function detectMappingType(string $directory, ContainerBuilder $containe /** * Loads a configured object manager metadata, query or result cache driver. * + * @return void + * * @throws \InvalidArgumentException in case of unknown driver type */ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, string $cacheName) diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php index aa76d7d2da8e5..83bfffaf2724e 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php @@ -28,6 +28,9 @@ public function __construct(string $managerType) $this->managerType = $managerType; } + /** + * @return void + */ public function process(ContainerBuilder $container) { $this->updateValidatorMappingFiles($container, 'xml', 'xml'); @@ -38,7 +41,7 @@ public function process(ContainerBuilder $container) * Gets the validation mapping files for the format and extends them with * files matching a doctrine search pattern (Resources/config/validation.orm.xml). */ - private function updateValidatorMappingFiles(ContainerBuilder $container, string $mapping, string $extension) + private function updateValidatorMappingFiles(ContainerBuilder $container, string $mapping, string $extension): void { if (!$container->hasParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files')) { return; diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index 30d0fb37ced80..16a37b524acc6 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -106,6 +106,7 @@ private function addTaggedServices(ContainerBuilder $container): array $refs = $managerDef->getArguments()[1] ?? []; $listenerRefs[$con][$id] = new Reference($id); if ($subscriberTag === $tagName) { + trigger_deprecation('symfony/doctrine-bridge', '6.3', 'Using Doctrine subscribers as services is deprecated, declare listeners instead'); $refs[] = $id; } else { $refs[] = [[$tag['event']], $id]; @@ -124,7 +125,7 @@ private function addTaggedServices(ContainerBuilder $container): array return $listenerRefs; } - private function getEventManagerDef(ContainerBuilder $container, string $name) + private function getEventManagerDef(ContainerBuilder $container, string $name): Definition { if (!isset($this->eventManagers[$name])) { $this->eventManagers[$name] = $container->getDefinition(sprintf($this->managerTemplate, $name)); diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php index 0aeb25d287488..1a3f227c6d100 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php @@ -130,6 +130,8 @@ public function __construct(Definition|Reference $driver, array $namespaces, arr /** * Register mappings and alias with the metadata drivers. + * + * @return void */ public function process(ContainerBuilder $container) { diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php index 1ea0470bf8d0b..fd27f60ceb4f5 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php @@ -19,7 +19,7 @@ final class RegisterUidTypePass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!class_exists(AbstractUid::class)) { return; diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php index 73d5a9d654d4f..80ee258438d24 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php @@ -33,6 +33,9 @@ public function __construct(string $key, string $providerId) $this->providerId = $providerId; } + /** + * @return void + */ public function create(ContainerBuilder $container, string $id, array $config) { $container @@ -43,11 +46,17 @@ public function create(ContainerBuilder $container, string $id, array $config) ; } + /** + * @return string + */ public function getKey() { return $this->key; } + /** + * @return void + */ public function addConfiguration(NodeDefinition $node) { $node diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php index 30ea98880887d..52cc766ac9c5d 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php @@ -41,7 +41,7 @@ public function __construct(ObjectManager $manager, string $class, IdReader $idR $classMetadata = $manager->getClassMetadata($class); if ($idReader && !$idReader->isSingleId()) { - throw new \InvalidArgumentException(sprintf('The second argument `$idReader` of "%s" must be null when the query cannot be optimized because of composite id fields.', __METHOD__)); + throw new \InvalidArgumentException(sprintf('The second argument "$idReader" of "%s" must be null when the query cannot be optimized because of composite id fields.', __METHOD__)); } $this->manager = $manager; diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index 7381a6a992679..c8b341f0edddb 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -74,16 +74,12 @@ public function getEntitiesByIds(string $identifier, array $values): array // Filter out non-integer values (e.g. ""). If we don't, some // databases such as PostgreSQL fail. - $values = array_values(array_filter($values, function ($v) { - return (string) $v === (string) (int) $v || ctype_digit($v); - })); + $values = array_values(array_filter($values, fn ($v) => (string) $v === (string) (int) $v || ctype_digit($v))); } elseif (\in_array($type, ['ulid', 'uuid', 'guid'])) { $parameterType = Connection::PARAM_STR_ARRAY; // Like above, but we just filter out empty strings. - $values = array_values(array_filter($values, function ($v) { - return '' !== (string) $v; - })); + $values = array_values(array_filter($values, fn ($v) => '' !== (string) $v)); // Convert values into right type if (Type::hasType($type)) { diff --git a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php index f16fc64fd3ff4..cff8b3b156154 100644 --- a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php +++ b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php @@ -38,6 +38,9 @@ public static function getSubscribedEvents(): array ]; } + /** + * @return void + */ public function onSubmit(FormEvent $event) { $collection = $event->getForm()->getData(); diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 9f7eed373df6f..d1d72ef75a922 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -97,6 +97,9 @@ public function __construct(ManagerRegistry $registry) $this->registry = $registry; } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { if ($options['multiple'] && interface_exists(Collection::class)) { @@ -107,6 +110,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $choiceLoader = function (Options $options) { @@ -193,15 +199,13 @@ public function configureOptions(OptionsResolver $resolver) // Set the "id_reader" option via the normalizer. This option is not // supposed to be set by the user. - $idReaderNormalizer = function (Options $options) { - // The ID reader is a utility that is needed to read the object IDs - // when generating the field values. The callback generating the - // field values has no access to the object manager or the class - // of the field, so we store that information in the reader. - // The reader is cached so that two choice lists for the same class - // (and hence with the same reader) can successfully be cached. - return $this->getCachedIdReader($options['em'], $options['class']); - }; + // The ID reader is a utility that is needed to read the object IDs + // when generating the field values. The callback generating the + // field values has no access to the object manager or the class + // of the field, so we store that information in the reader. + // The reader is cached so that two choice lists for the same class + // (and hence with the same reader) can successfully be cached. + $idReaderNormalizer = fn (Options $options) => $this->getCachedIdReader($options['em'], $options['class']); $resolver->setDefaults([ 'em' => null, @@ -234,6 +238,9 @@ public function getParent(): string return ChoiceType::class; } + /** + * @return void + */ public function reset() { $this->idReaders = []; diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index fb45f99ac2657..c096b558db891 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -21,6 +21,9 @@ class EntityType extends DoctrineType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { parent::configureOptions($resolver); diff --git a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php index f715e782e3af7..b2369e95d601a 100644 --- a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +++ b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php @@ -48,6 +48,8 @@ public function stopQuery(): void /** * Logs a message. + * + * @return void */ protected function log(string $message, array $params) { diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php index 9b464351fa09d..38618fc15e5ba 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php @@ -30,11 +30,17 @@ public function __construct(ManagerRegistry $managerRegistry) $this->managerRegistry = $managerRegistry; } + /** + * @return void + */ public function onWorkerMessageHandled() { $this->clearEntityManagers(); } + /** + * @return void + */ public function onWorkerMessageFailed() { $this->clearEntityManagers(); @@ -48,7 +54,7 @@ public static function getSubscribedEvents(): array ]; } - private function clearEntityManagers() + private function clearEntityManagers(): void { foreach ($this->managerRegistry->getManagers() as $manager) { $manager->clear(); diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php index 105a1c6ffe76e..1831715039d2e 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php @@ -33,7 +33,7 @@ protected function handleForManager(EntityManagerInterface $entityManager, Envel return $stack->next()->handle($envelope, $stack); } - private function pingConnection(EntityManagerInterface $entityManager) + private function pingConnection(EntityManagerInterface $entityManager): void { $connection = $entityManager->getConnection(); diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index 75682577ed2fc..c567fc37fd835 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -47,9 +47,7 @@ public function getProperties(string $class, array $context = []): ?array $properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames()); if ($metadata instanceof ClassMetadataInfo && class_exists(Embedded::class) && $metadata->embeddedClasses) { - $properties = array_filter($properties, function ($property) { - return !str_contains($property, '.'); - }); + $properties = array_filter($properties, fn ($property) => !str_contains($property, '.')); $properties = array_merge($properties, array_keys($metadata->embeddedClasses)); } diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php b/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php new file mode 100644 index 0000000000000..04907ee9a78bd --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; + +abstract class AbstractSchemaListener +{ + abstract public function postGenerateSchema(GenerateSchemaEventArgs $event): void; + + protected function getIsSameDatabaseChecker(Connection $connection): \Closure + { + return static function (\Closure $exec) use ($connection): bool { + $checkTable = 'schema_subscriber_check_'.bin2hex(random_bytes(7)); + $connection->executeStatement(sprintf('CREATE TABLE %s (id INTEGER NOT NULL)', $checkTable)); + + try { + $exec(sprintf('DROP TABLE %s', $checkTable)); + } catch (\Exception) { + // ignore + } + + try { + $connection->executeStatement(sprintf('DROP TABLE %s', $checkTable)); + + return false; + } catch (TableNotFoundException) { + return true; + } + }; + } +} diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaListener.php b/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaListener.php new file mode 100644 index 0000000000000..7be883db807a8 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaListener.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; + +/** + * Automatically adds the cache table needed for the DoctrineDbalAdapter of + * the Cache component. + */ +class DoctrineDbalCacheAdapterSchemaListener extends AbstractSchemaListener +{ + /** + * @param iterable $dbalAdapters + */ + public function __construct(private iterable $dbalAdapters) + { + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + $connection = $event->getEntityManager()->getConnection(); + + foreach ($this->dbalAdapters as $dbalAdapter) { + $dbalAdapter->configureSchema($event->getSchema(), $connection, $this->getIsSameDatabaseChecker($connection)); + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriber.php index bf9b793175f3f..9aa98ebb5b9ba 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriber.php @@ -12,36 +12,20 @@ namespace Symfony\Bridge\Doctrine\SchemaListener; use Doctrine\Common\EventSubscriber; -use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use Doctrine\ORM\Tools\ToolEvents; -use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; + +trigger_deprecation('symfony/doctrine-bridge', '6.3', 'The "%s" class is deprecated. Use "%s" instead.', DoctrineDbalCacheAdapterSchemaSubscriber::class, DoctrineDbalCacheAdapterSchemaListener::class); /** * Automatically adds the cache table needed for the DoctrineDbalAdapter of * the Cache component. * * @author Ryan Weaver + * + * @deprecated since Symfony 6.3, use {@link DoctrineDbalCacheAdapterSchemaListener} instead */ -final class DoctrineDbalCacheAdapterSchemaSubscriber implements EventSubscriber +final class DoctrineDbalCacheAdapterSchemaSubscriber extends DoctrineDbalCacheAdapterSchemaListener implements EventSubscriber { - private $dbalAdapters; - - /** - * @param iterable $dbalAdapters - */ - public function __construct(iterable $dbalAdapters) - { - $this->dbalAdapters = $dbalAdapters; - } - - public function postGenerateSchema(GenerateSchemaEventArgs $event): void - { - $dbalConnection = $event->getEntityManager()->getConnection(); - foreach ($this->dbalAdapters as $dbalAdapter) { - $dbalAdapter->configureSchema($event->getSchema(), $dbalConnection); - } - } - public function getSubscribedEvents(): array { if (!class_exists(ToolEvents::class)) { diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/LockStoreSchemaListener.php b/src/Symfony/Bridge/Doctrine/SchemaListener/LockStoreSchemaListener.php new file mode 100644 index 0000000000000..0902b376d8968 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/LockStoreSchemaListener.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Component\Lock\PersistingStoreInterface; +use Symfony\Component\Lock\Store\DoctrineDbalStore; + +final class LockStoreSchemaListener extends AbstractSchemaListener +{ + /** + * @param iterable $stores + */ + public function __construct(private iterable $stores) + { + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + $connection = $event->getEntityManager()->getConnection(); + + foreach ($this->stores as $store) { + if (!$store instanceof DoctrineDbalStore) { + continue; + } + + $store->configureSchema($event->getSchema(), $this->getIsSameDatabaseChecker($connection)); + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaListener.php b/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaListener.php new file mode 100644 index 0000000000000..f5416115cba8f --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaListener.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport; +use Symfony\Component\Messenger\Transport\TransportInterface; + +/** + * Automatically adds any required database tables to the Doctrine Schema. + */ +class MessengerTransportDoctrineSchemaListener extends AbstractSchemaListener +{ + private const PROCESSING_TABLE_FLAG = self::class.':processing'; + + /** + * @param iterable $transports + */ + public function __construct(private iterable $transports) + { + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + $connection = $event->getEntityManager()->getConnection(); + + foreach ($this->transports as $transport) { + if (!$transport instanceof DoctrineTransport) { + continue; + } + + $transport->configureSchema($event->getSchema(), $connection, $this->getIsSameDatabaseChecker($connection)); + } + } + + public function onSchemaCreateTable(SchemaCreateTableEventArgs $event): void + { + $table = $event->getTable(); + + // if this method triggers a nested create table below, allow Doctrine to work like normal + if ($table->hasOption(self::PROCESSING_TABLE_FLAG)) { + return; + } + + foreach ($this->transports as $transport) { + if (!$transport instanceof DoctrineTransport) { + continue; + } + + if (!$extraSql = $transport->getExtraSetupSqlForTable($table)) { + continue; + } + + // avoid this same listener from creating a loop on this table + $table->addOption(self::PROCESSING_TABLE_FLAG, true); + $createTableSql = $event->getPlatform()->getCreateTableSQL($table); + + /* + * Add all the SQL needed to create the table and tell Doctrine + * to "preventDefault" so that only our SQL is used. This is + * the only way to inject some extra SQL. + */ + $event->addSql($createTableSql); + foreach ($extraSql as $sql) { + $event->addSql($sql); + } + $event->preventDefault(); + + return; + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php index 3cf100615a51c..10b2372ab161e 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php @@ -12,81 +12,20 @@ namespace Symfony\Bridge\Doctrine\SchemaListener; use Doctrine\Common\EventSubscriber; -use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; use Doctrine\DBAL\Events; -use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use Doctrine\ORM\Tools\ToolEvents; -use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport; -use Symfony\Component\Messenger\Transport\TransportInterface; + +trigger_deprecation('symfony/doctrine-bridge', '6.3', 'The "%s" class is deprecated. Use "%s" instead.', MessengerTransportDoctrineSchemaSubscriber::class, MessengerTransportDoctrineSchemaListener::class); /** * Automatically adds any required database tables to the Doctrine Schema. * * @author Ryan Weaver + * + * @deprecated since Symfony 6.3, use {@link MessengerTransportDoctrineSchemaListener} instead */ -final class MessengerTransportDoctrineSchemaSubscriber implements EventSubscriber +final class MessengerTransportDoctrineSchemaSubscriber extends MessengerTransportDoctrineSchemaListener implements EventSubscriber { - private const PROCESSING_TABLE_FLAG = self::class.':processing'; - - private iterable $transports; - - /** - * @param iterable $transports - */ - public function __construct(iterable $transports) - { - $this->transports = $transports; - } - - public function postGenerateSchema(GenerateSchemaEventArgs $event): void - { - $dbalConnection = $event->getEntityManager()->getConnection(); - foreach ($this->transports as $transport) { - if (!$transport instanceof DoctrineTransport) { - continue; - } - - $transport->configureSchema($event->getSchema(), $dbalConnection); - } - } - - public function onSchemaCreateTable(SchemaCreateTableEventArgs $event): void - { - $table = $event->getTable(); - - // if this method triggers a nested create table below, allow Doctrine to work like normal - if ($table->hasOption(self::PROCESSING_TABLE_FLAG)) { - return; - } - - foreach ($this->transports as $transport) { - if (!$transport instanceof DoctrineTransport) { - continue; - } - - if (!$extraSql = $transport->getExtraSetupSqlForTable($table)) { - continue; - } - - // avoid this same listener from creating a loop on this table - $table->addOption(self::PROCESSING_TABLE_FLAG, true); - $createTableSql = $event->getPlatform()->getCreateTableSQL($table); - - /* - * Add all the SQL needed to create the table and tell Doctrine - * to "preventDefault" so that only our SQL is used. This is - * the only way to inject some extra SQL. - */ - $event->addSql($createTableSql); - foreach ($extraSql as $sql) { - $event->addSql($sql); - } - $event->preventDefault(); - - return; - } - } - public function getSubscribedEvents(): array { $subscribedEvents = []; diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaListener.php b/src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaListener.php new file mode 100644 index 0000000000000..5035743080e9d --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/PdoSessionHandlerSchemaListener.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + +final class PdoSessionHandlerSchemaListener extends AbstractSchemaListener +{ + private PdoSessionHandler $sessionHandler; + + public function __construct(\SessionHandlerInterface $sessionHandler) + { + if ($sessionHandler instanceof PdoSessionHandler) { + $this->sessionHandler = $sessionHandler; + } + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + if (!isset($this->sessionHandler)) { + return; + } + + $connection = $event->getEntityManager()->getConnection(); + + $this->sessionHandler->configureSchema($event->getSchema(), $this->getIsSameDatabaseChecker($connection)); + } +} diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaListener.php b/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaListener.php new file mode 100644 index 0000000000000..a7f4c49d58784 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaListener.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\SchemaListener; + +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider; +use Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler; +use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; + +/** + * Automatically adds the rememberme table needed for the {@see DoctrineTokenProvider}. + */ +class RememberMeTokenProviderDoctrineSchemaListener extends AbstractSchemaListener +{ + /** + * @param iterable $rememberMeHandlers + */ + public function __construct(private iterable $rememberMeHandlers) + { + } + + public function postGenerateSchema(GenerateSchemaEventArgs $event): void + { + $connection = $event->getEntityManager()->getConnection(); + + foreach ($this->rememberMeHandlers as $rememberMeHandler) { + if ( + $rememberMeHandler instanceof PersistentRememberMeHandler + && ($tokenProvider = $rememberMeHandler->getTokenProvider()) instanceof DoctrineTokenProvider + ) { + $tokenProvider->configureSchema($event->getSchema(), $connection, $this->getIsSameDatabaseChecker($connection)); + } + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaSubscriber.php index 2eba94ff23c06..82a5a7817b7b5 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/RememberMeTokenProviderDoctrineSchemaSubscriber.php @@ -12,43 +12,20 @@ namespace Symfony\Bridge\Doctrine\SchemaListener; use Doctrine\Common\EventSubscriber; -use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use Doctrine\ORM\Tools\ToolEvents; use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider; -use Symfony\Component\Security\Http\RememberMe\PersistentRememberMeHandler; -use Symfony\Component\Security\Http\RememberMe\RememberMeHandlerInterface; + +trigger_deprecation('symfony/doctrine-bridge', '6.3', 'The "%s" class is deprecated. Use "%s" instead.', RememberMeTokenProviderDoctrineSchemaSubscriber::class, RememberMeTokenProviderDoctrineSchemaListener::class); /** * Automatically adds the r 10000 ememberme table needed for the {@see DoctrineTokenProvider}. * * @author Wouter de Jong + * + * @deprecated since Symfony 6.3, use {@link RememberMeTokenProviderDoctrineSchemaListener} instead */ -final class RememberMeTokenProviderDoctrineSchemaSubscriber implements EventSubscriber +final class RememberMeTokenProviderDoctrineSchemaSubscriber extends RememberMeTokenProviderDoctrineSchemaListener implements EventSubscriber { - private iterable $rememberMeHandlers; - - /** - * @param iterable $rememberMeHandlers - */ - public function __construct(iterable $rememberMeHandlers) - { - $this->rememberMeHandlers = $rememberMeHandlers; - } - - public function postGenerateSchema(GenerateSchemaEventArgs $event): void - { - $dbalConnection = $event->getEntityManager()->getConnection(); - - foreach ($this->rememberMeHandlers as $rememberMeHandler) { - if ( - $rememberMeHandler instanceof PersistentRememberMeHandler - && ($tokenProvider = $rememberMeHandler->getTokenProvider()) instanceof DoctrineTokenProvider - ) { - $tokenProvider->configureSchema($event->getSchema(), $dbalConnection); - } - } - } - public function getSubscribedEvents(): array { if (!class_exists(ToolEvents::class)) { diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index 60f883a0e465f..9d61be61bd3a7 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -66,6 +66,9 @@ public function loadTokenBySeries(string $series): PersistentTokenInterface throw new TokenNotFoundException('No token found.'); } + /** + * @return void + */ public function deleteTokenBySeries(string $series) { $sql = 'DELETE FROM rememberme_token WHERE series=:series'; @@ -78,6 +81,9 @@ public function deleteTokenBySeries(string $series) } } + /** + * @return void + */ public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTime $lastUsed) { $sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed WHERE series=:series'; @@ -101,6 +107,9 @@ public function updateToken(string $series, #[\SensitiveParameter] string $token } } + /** + * @return void + */ public function createNewToken(PersistentTokenInterface $token) { $sql = 'INSERT INTO rememberme_token (class, username, series, value, lastUsed) VALUES (:class, :username, :series, :value, :lastUsed)'; @@ -188,15 +197,18 @@ public function updateExistingToken(PersistentTokenInterface $token, #[\Sensitiv /** * Adds the Table to the Schema if "remember me" uses this Connection. + * + * @param \Closure $isSameDatabase */ - public function configureSchema(Schema $schema, Connection $forConnection): void + public function configureSchema(Schema $schema, Connection $forConnection/* , \Closure $isSameDatabase */): void { - // only update the schema for this connection - if ($forConnection !== $this->conn) { + if ($schema->hasTable('rememberme_token')) { return; } - if ($schema->hasTable('rememberme_token')) { + $isSameDatabase = 2 < \func_num_args() ? func_get_arg(2) : static fn () => false; + + if ($forConnection !== $this->conn && !$isSameDatabase($this->conn->executeStatement(...))) { return; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php index 0ad18f0a66edd..f215f4c774034 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php @@ -32,22 +32,30 @@ protected function setUp(): void public function testDispatchEventRespectOrder() { - $this->evm = new ContainerAwareEventManager($this->container, ['sub1', [['foo'], 'list1'], 'sub2']); + $this->evm = new ContainerAwareEventManager($this->container, [[['foo'], 'list1'], [['foo'], 'list2']]); $this->container->set('list1', $listener1 = new MyListener()); + $this->container->set('list2', $listener2 = new MyListener()); + + $this->assertSame([$listener1, $listener2], array_values($this->evm->getListeners('foo'))); + } + + /** + * @group legacy + */ + public function testDispatchEventRespectOrderWithSubscribers() + { + $this->evm = new ContainerAwareEventManager($this->container, ['sub1', 'sub2']); + $this->container->set('sub1', $subscriber1 = new MySubscriber(['foo'])); $this->container->set('sub2', $subscriber2 = new MySubscriber(['foo'])); - $this->assertSame([$subscriber1, $listener1, $subscriber2], array_values($this->evm->getListeners('foo'))); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); + $this->assertSame([$subscriber1, $subscriber2], array_values($this->evm->getListeners('foo'))); } public function testDispatchEvent() { - $this->evm = new ContainerAwareEventManager($this->container, ['lazy4']); - - $this->container->set('lazy4', $subscriber1 = new MySubscriber(['foo'])); - $this->assertSame(0, $subscriber1->calledSubscribedEventsCount); - $this->container->set('lazy1', $listener1 = new MyListener()); $this->evm->addEventListener('foo', 'lazy1'); $this->evm->addEventListener('foo', $listener2 = new MyListener()); @@ -57,16 +65,10 @@ public function testDispatchEvent() $this->container->set('lazy3', $listener5 = new MyListener()); $this->evm->addEventListener('foo', $listener5 = new MyListener()); $this->evm->addEventListener('bar', $listener5); - $this->evm->addEventSubscriber($subscriber2 = new MySubscriber(['bar'])); - - $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); $this->evm->dispatchEvent('foo'); $this->evm->dispatchEvent('bar'); - $this->assertSame(1, $subscriber1->calledSubscribedEventsCount); - $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); - $this->assertSame(0, $listener1->calledByInvokeCount); $this->assertSame(1, $listener1->calledByEventNameCount); $this->assertSame(0, $listener2->calledByInvokeCount); @@ -77,23 +79,46 @@ public function testDispatchEvent() $this->assertSame(0, $listener4->calledByEventNameCount); $this->assertSame(1, $listener5->calledByInvokeCount); $this->assertSame(1, $listener5->calledByEventNameCount); - $this->assertSame(0, $subscriber1->calledByInvokeCount); - $this->assertSame(1, $subscriber1->calledByEventNameCount); - $this->assertSame(1, $subscriber2->calledByInvokeCount); - $this->assertSame(0, $subscriber2->calledByEventNameCount); } - public function testAddEventListenerAndSubscriberAfterDispatchEvent() + /** + * @group legacy + */ + public function testDispatchEventWithSubscribers() { - $this->evm = new ContainerAwareEventManager($this->container, ['lazy7']); + $this->evm = new ContainerAwareEventManager($this->container, ['lazy4']); - $this->container->set('lazy7', $subscriber1 = new MySubscriber(['foo'])); + $this->container->set('lazy4', $subscriber1 = new MySubscriber(['foo'])); $this->assertSame(0, $subscriber1->calledSubscribedEventsCount); $this->container->set('lazy1', $listener1 = new MyListener()); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); $this->evm->addEventListener('foo', 'lazy1'); + $this->evm->addEventListener('foo', $listener2 = new MyListener()); + $this->evm->addEventSubscriber($subscriber2 = new MySubscriber(['bar'])); + + $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); + + $this->evm->dispatchEvent('foo'); + $this->evm->dispatchEvent('bar'); + $this->assertSame(1, $subscriber1->calledSubscribedEventsCount); + $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); + + $this->assertSame(0, $listener1->calledByInvokeCount); + $this->assertSame(1, $listener1->calledByEventNameCount); + $this->assertSame(0, $listener2->calledByInvokeCount); + $this->assertSame(1, $listener2->calledByEventNameCount); + $this->assertSame(0, $subscriber1->calledByInvokeCount); + $this->assertSame(1, $subscriber1->calledByEventNameCount); + $this->assertSame(1, $subscriber2->calledByInvokeCount); + $this->assertSame(0, $subscriber2->calledByEventNameCount); + } + public function testAddEventListenerAfterDispatchEvent() + { + $this->container->set('lazy1', $listener1 = new MyListener()); + $this->evm->addEventListener('foo', 'lazy1'); $this->evm->addEventListener('foo', $listener2 = new MyListener()); $this->container->set('lazy2', $listener3 = new MyListener()); $this->evm->addEventListener('bar', 'lazy2'); @@ -101,16 +126,10 @@ public function testAddEventListenerAndSubscriberAfterDispatchEvent() $this->container->set('lazy3', $listener5 = new MyListener()); $this->evm->addEventListener('foo', $listener5 = new MyListener()); $this->evm->addEventListener('bar', $listener5); - $this->evm->addEventSubscriber($subscriber2 = new MySubscriber(['bar'])); - - $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); $this->evm->dispatchEvent('foo'); $this->evm->dispatchEvent('bar'); - $this->assertSame(1, $subscriber1->calledSubscribedEventsCount); - $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); - $this->container->set('lazy4', $listener6 = new MyListener()); $this->evm->addEventListener('foo', 'lazy4'); $this->evm->addEventListener('foo', $listener7 = new MyListener()); @@ -120,19 +139,10 @@ public function testAddEventListenerAndSubscriberAfterDispatchEvent() $this->container->set('lazy6', $listener10 = new MyListener()); $this->evm->addEventListener('foo', $listener10 = new MyListener()); $this->evm->addEventListener('bar', $listener10); - $this->evm->addEventSubscriber($subscriber3 = new MySubscriber(['bar'])); - - $this->assertSame(1, $subscriber1->calledSubscribedEventsCount); - $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); - $this->assertSame(1, $subscriber3->calledSubscribedEventsCount); $this->evm->dispatchEvent('foo'); $this->evm->dispatchEvent('bar'); - $this->assertSame(1, $subscriber1->calledSubscribedEventsCount); - $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); - $this->assertSame(1, $subscriber3->calledSubscribedEventsCount); - $this->assertSame(0, $listener1->calledByInvokeCount); $this->assertSame(2, $listener1->calledByEventNameCount); $this->assertSame(0, $listener2->calledByInvokeCount); @@ -143,10 +153,6 @@ public function testAddEventListenerAndSubscriberAfterDispatchEvent() $this->assertSame(0, $listener4->calledByEventNameCount); $this->assertSame(2, $listener5->calledByInvokeCount); $this->assertSame(2, $listener5->calledByEventNameCount); - $this->assertSame(0, $subscriber1->calledByInvokeCount); - $this->assertSame(2, $subscriber1->calledByEventNameCount); - $this->assertSame(2, $subscriber2->calledByInvokeCount); - $this->assertSame(0, $subscriber2->calledByEventNameCount); $this->assertSame(0, $listener6->calledByInvokeCount); $this->assertSame(1, $listener6->calledByEventNameCount); @@ -158,16 +164,81 @@ public function testAddEventListenerAndSubscriberAfterDispatchEvent() $this->assertSame(0, $listener9->calledByEventNameCount); $this->assertSame(1, $listener10->calledByInvokeCount); $this->assertSame(1, $listener10->calledByEventNameCount); + } + + /** + * @group legacy + */ + public function testAddEventListenerAndSubscriberAfterDispatchEvent() + { + $this->evm = new ContainerAwareEventManager($this->container, ['lazy7']); + + $this->container->set('lazy7', $subscriber1 = new MySubscriber(['foo'])); + $this->assertSame(0, $subscriber1->calledSubscribedEventsCount); + + $this->container->set('lazy1', $listener1 = new MyListener()); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); + $this->evm->addEventListener('foo', 'lazy1'); + $this->assertSame(1, $subscriber1->calledSubscribedEventsCount); + + $this->evm->addEventSubscriber($subscriber2 = new MySubscriber(['bar'])); + + $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); + + $this->evm->dispatchEvent('foo'); + $this->evm->dispatchEvent('bar'); + + $this->assertSame(1, $subscriber1->calledSubscribedEventsCount); + $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); + + $this->container->set('lazy6', $listener2 = new MyListener()); + $this->evm->addEventListener('foo', $listener2 = new MyListener()); + $this->evm->addEventListener('bar', $listener2); + $this->evm->addEventSubscriber($subscriber3 = new MySubscriber(['bar'])); + + $this->assertSame(1, $subscriber1->calledSubscribedEventsCount); + $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); + $this->assertSame(1, $subscriber3->calledSubscribedEventsCount); + + $this->evm->dispatchEvent('foo'); + $this->evm->dispatchEvent('bar'); + + $this->assertSame(1, $subscriber1->calledSubscribedEventsCount); + $this->assertSame(1, $subscriber2->calledSubscribedEventsCount); + $this->assertSame(1, $subscriber3->calledSubscribedEventsCount); + + $this->assertSame(0, $listener1->calledByInvokeCount); + $this->assertSame(2, $listener1->calledByEventNameCount); + $this->assertSame(0, $subscriber1->calledByInvokeCount); + $this->assertSame(2, $subscriber1->calledByEventNameCount); + $this->assertSame(2, $subscriber2->calledByInvokeCount); + $this->assertSame(0, $subscriber2->calledByEventNameCount); + + $this->assertSame(1, $listener2->calledByInvokeCount); + $this->assertSame(1, $listener2->calledByEventNameCount); $this->assertSame(1, $subscriber3->calledByInvokeCount); $this->assertSame(0, $subscriber3->calledByEventNameCount); } public function testGetListenersForEvent() + { + $this->container->set('lazy', $listener1 = new MyListener()); + $this->evm->addEventListener('foo', 'lazy'); + $this->evm->addEventListener('foo', $listener2 = new MyListener()); + + $this->assertSame([$listener1, $listener2], array_values($this->evm->getListeners('foo'))); + } + + /** + * @group legacy + */ + public function testGetListenersForEventWhenSubscribersArePresent() { $this->evm = new ContainerAwareEventManager($this->container, ['lazy2']); $this->container->set('lazy', $listener1 = new MyListener()); $this->container->set('lazy2', $subscriber1 = new MySubscriber(['foo'])); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); $this->evm->addEventListener('foo', 'lazy'); $this->evm->addEventListener('foo', $listener2 = new MyListener()); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php index e6fd198920517..02cd5acf0365d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\ContainerAwareEventManager; use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -22,6 +23,8 @@ class RegisterEventListenersAndSubscribersPassTest extends TestCase { + use ExpectDeprecationTrait; + public function testExceptionOnAbstractTaggedSubscriber() { $this->expectException(\InvalidArgumentException::class); @@ -195,6 +198,9 @@ public function testProcessEventListenersWithMultipleConnections() ); } + /** + * @group legacy + */ public function testProcessEventSubscribersWithMultipleConnections() { $container = $this->createBuilder(true); @@ -232,6 +238,7 @@ public function testProcessEventSubscribersWithMultipleConnections() ]) ; + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); $this->process($container); $eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager'); @@ -279,6 +286,9 @@ public function testProcessEventSubscribersWithMultipleConnections() ); } + /** + * @group legacy + */ public function testProcessEventSubscribersWithPriorities() { $container = $this->createBuilder(); @@ -312,6 +322,7 @@ public function testProcessEventSubscribersWithPriorities() ]) ; + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); $this->process($container); $eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager'); @@ -341,6 +352,9 @@ public function testProcessEventSubscribersWithPriorities() ); } + /** + * @group legacy + */ public function testProcessEventSubscribersAndListenersWithPriorities() { $container = $this->createBuilder(); @@ -402,6 +416,7 @@ public function testProcessEventSubscribersAndListenersWithPriorities() ]) ; + $this->expectDeprecation('Since symfony/doctrine-bridge 6.3: Using Doctrine subscribers as services is deprecated, declare listeners instead'); $this->process($container); $eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager'); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php index 2a77216552be6..d816ee2303a01 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -47,9 +47,7 @@ protected function setUp(): void $this->extension->expects($this->any()) ->method('getObjectManagerElementName') - ->willReturnCallback(function ($name) { - return 'doctrine.orm.'.$name; - }); + ->willReturnCallback(fn ($name) => 'doctrine.orm.'.$name); $this->extension ->method('getMappingObjectDefaultName') @@ -77,7 +75,7 @@ public function testFixManagersAutoMappingsWithTwoAutomappings() 'SecondBundle' => 'My\SecondBundle', ]; - $reflection = new \ReflectionClass(\get_class($this->extension)); + $reflection = new \ReflectionClass($this->extension); $method = $reflection->getMethod('fixManagersAutoMappings'); $method->invoke($this->extension, $emConfigs, $bundles); @@ -165,7 +163,7 @@ public function testFixManagersAutoMappings(array $originalEm1, array $originalE 'SecondBundle' => 'My\SecondBundle', ]; - $reflection = new \ReflectionClass(\get_class($this->extension)); + $reflection = new \ReflectionClass($this->extension); $method = $reflection->getMethod('fixManagersAutoMappings'); $newEmConfigs = $method->invoke($this->extension, $emConfigs, $bundles); @@ -182,7 +180,7 @@ public function testMappingTypeDetection() { $container = $this->createContainer(); - $reflection = new \ReflectionClass(\get_class($this->extension)); + $reflection = new \ReflectionClass($this->extension); $method = $reflection->getMethod('detectMappingType'); // The ordinary fixtures contain annotation @@ -327,7 +325,7 @@ public function testBundleAutoMapping(string $bundle, string $expectedType, stri $container = $this->createContainer([], [$bundle => $bundleClassName]); - $reflection = new \ReflectionClass(\get_class($this->extension)); + $reflection = new \ReflectionClass($this->extension); $method = $reflection->getMethod('getMappingDriverBundleConfigDefaults'); $this->assertSame( diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php index fbad8e05c56f6..1f2f60b61c6bc 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php @@ -18,14 +18,14 @@ class ContainerAwareFixture implements FixtureInterface, ContainerAwareInterface { - public $container; + public ?ContainerInterface $container = null; - public function setContainer(?ContainerInterface $container) + public function setContainer(?ContainerInterface $container): void { $this->container = $container; } - public function load(ObjectManager $manager) + public function load(ObjectManager $manager): void { } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php index b8b10094dd6bd..02d31078451b8 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php @@ -54,7 +54,7 @@ public function getUserIdentifier(): string return $this->name; } - public function eraseCredentials() + public function eraseCredentials(): void { } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php index bdbb3e9a9b4fd..7205799b8fa7a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php @@ -222,7 +222,7 @@ public function testLoadValuesForChoicesDoesNotLoadIfSingleIntIdAndValueGiven() ); $choices = [$this->obj1, $this->obj2, $this->obj3]; - $value = function (\stdClass $object) { return $object->name; }; + $value = fn (\stdClass $object) => $object->name; $this->repository->expects($this->never()) ->method('findAll') @@ -367,7 +367,7 @@ public function testLoadChoicesForValuesLoadsAllIfSingleIntIdAndValueGiven() ); $choices = [$this->obj1, $this->obj2, $this->obj3]; - $value = function (\stdClass $object) { return $object->name; }; + $value = fn (\stdClass $object) => $object->name; $this->repository->expects($this->once()) ->method('findAll') @@ -416,7 +416,7 @@ public function testLoadChoicesForValuesLoadsOnlyChoicesIfValueIsIdReader() public function testPassingIdReaderWithoutSingleIdEntity() { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The second argument `$idReader` of "Symfony\\Bridge\\Doctrine\\Form\\ChoiceList\\DoctrineChoiceLoader::__construct" must be null when the query cannot be optimized because of composite id fields.'); + $this->expectExceptionMessage('The second argument "$idReader" of "Symfony\\Bridge\\Doctrine\\Form\\ChoiceList\\DoctrineChoiceLoader::__construct" must be null when the query cannot be optimized because of composite id fields.'); $idReader = $this->createMock(IdReader::class); $idReader->expects($this->once()) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php index 3a626bb1e02d4..c1cef742e3d28 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\GuidType; @@ -19,6 +20,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; use Symfony\Bridge\Doctrine\Types\UlidType; use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Component\Form\Exception\TransformationFailedException; @@ -35,12 +38,12 @@ protected function tearDown(): void public function testIdentifierTypeIsStringArray() { - $this->checkIdentifierType('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity', Connection::PARAM_STR_ARRAY); + $this->checkIdentifierType(SingleStringIdEntity::class, class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY); } public function testIdentifierTypeIsIntegerArray() { - $this->checkIdentifierType('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity', Connection::PARAM_INT_ARRAY); + $this->checkIdentifierType(SingleIntIdEntity::class, class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY); } protected function checkIdentifierType($classname, $expectedType) @@ -90,7 +93,7 @@ public function testFilterNonIntegerValues() $query->expects($this->once()) ->method('setParameter') - ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [1, 2, 3, '9223372036854775808'], Connection::PARAM_INT_ARRAY) + ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [1, 2, 3, '9223372036854775808'], class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY) ->willReturn($query); $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) @@ -126,7 +129,7 @@ public function testFilterEmptyUuids($entityClass) $query->expects($this->once()) ->method('setParameter') - ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', ['71c5fd46-3f16-4abb-bad7-90ac1e654a2d', 'b98e8e11-2897-44df-ad24-d2627eb7f499'], Connection::PARAM_STR_ARRAY) + ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', ['71c5fd46-3f16-4abb-bad7-90ac1e654a2d', 'b98e8e11-2897-44df-ad24-d2627eb7f499'], class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY) ->willReturn($query); $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) @@ -171,7 +174,7 @@ public function testFilterUid($entityClass) $query->expects($this->once()) ->method('setParameter') - ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [Uuid::fromString('71c5fd46-3f16-4abb-bad7-90ac1e654a2d')->toBinary(), Uuid::fromString('b98e8e11-2897-44df-ad24-d2627eb7f499')->toBinary()], Connection::PARAM_STR_ARRAY) + ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [Uuid::fromString('71c5fd46-3f16-4abb-bad7-90ac1e654a2d')->toBinary(), Uuid::fromString('b98e8e11-2897-44df-ad24-d2627eb7f499')->toBinary()], class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY) ->willReturn($query); $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) @@ -239,7 +242,7 @@ public function testEmbeddedIdentifierName() $query->expects($this->once()) ->method('setParameter') - ->with('ORMQueryBuilderLoader_getEntitiesByIds_id_value', [1, 2, 3], Connection::PARAM_INT_ARRAY) + ->with('ORMQueryBuilderLoader_getEntitiesByIds_id_value', [1, 2, 3], class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY) ->willReturn($query); $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) @@ -280,17 +283,11 @@ public function __construct() { } - /** - * @return array|string - */ - public function getSQL() + public function getSQL(): array|string { } - /** - * @return Result|int - */ - protected function _doExecute() + protected function _doExecute(): Result|int { } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 3d752afdb3283..0f7066609f6fb 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -236,9 +236,7 @@ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function () { - return new \stdClass(); - }, + 'query_builder' => fn () => new \stdClass(), ]); $field->submit('2'); @@ -1078,10 +1076,8 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureSingle $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repository) { - return $repository->createQueryBuilder('e') - ->where('e.id IN (1, 2)'); - }, + 'query_builder' => fn (EntityRepository $repository) => $repository->createQueryBuilder('e') + ->where('e.id IN (1, 2)'), 'choice_label' => 'name', ]); @@ -1102,10 +1098,8 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureCompos $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, [ 'em' => 'default', 'class' => self::COMPOSITE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repository) { - return $repository->createQueryBuilder('e') - ->where('e.id1 IN (10, 50)'); - }, + 'query_builder' => fn (EntityRepository $repository) => $repository->createQueryBuilder('e') + ->where('e.id1 IN (10, 50)'), 'choice_label' => 'name', ]); @@ -1220,17 +1214,13 @@ public function testLoaderCaching() $formBuilder->add('property2', static::TESTED_TYPE, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repo) { - return $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'); - }, + 'query_builder' => fn (EntityRepository $repo) => $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'), ]); $formBuilder->add('property3', static::TESTED_TYPE, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repo) { - return $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'); - }, + 'query_builder' => fn (EntityRepository $repo) => $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'), ]); $form = $formBuilder->getForm(); @@ -1280,17 +1270,13 @@ public function testLoaderCachingWithParameters() $formBuilder->add('property2', static::TESTED_TYPE, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repo) { - return $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1); - }, + 'query_builder' => fn (EntityRepository $repo) => $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1), ]); $formBuilder->add('property3', static::TESTED_TYPE, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function (EntityRepository $repo) { - return $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1); - }, + 'query_builder' => fn (EntityRepository $repo) => $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1), ]); $form = $formBuilder->getForm(); @@ -1791,9 +1777,7 @@ public function testWithSameLoaderAndDifferentChoiceValueCallbacks() ->add('entity_two', self::TESTED_TYPE, [ 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'choice_value' => function ($choice) { - return $choice ? $choice->name : ''; - }, + 'choice_value' => fn ($choice) => $choice ? $choice->name : '', ]) ->createView() ; diff --git a/src/Symfony/Bridge/Doctrine/Tests/LegacyManagerRegistryTest.php b/src/Symfony/Bridge/Doctrine/Tests/LegacyManagerRegistryTest.php new file mode 100644 index 0000000000000..7e525e35b1db4 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/LegacyManagerRegistryTest.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests; + +use PHPUnit\Framework\TestCase; +use ProxyManager\Proxy\LazyLoadingInterface; +use ProxyManager\Proxy\ValueHolderInterface; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +use Symfony\Bridge\ProxyManager\Tests\LazyProxy\Dumper\PhpDumperTest; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\Filesystem\Filesystem; + +/** + * @group legacy + */ +class LegacyManagerRegistryTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + $test = new PhpDumperTest(); + $test->testDumpContainerWithProxyServiceWillShareProxies(); + } + + public function testResetService() + { + $container = new \LazyServiceProjectServiceContainer(); + + $registry = new TestManagerRegistry('name', [], ['defaultManager' => 'foo'], 'defaultConnection', 'defaultManager', 'proxyInterfaceName'); + $registry->setTestContainer($container); + + $foo = $container->get('foo'); + $foo->bar = 123; + $this->assertTrue(isset($foo->bar)); + + $registry->resetManager(); + + $this->assertSame($foo, $container->get('foo')); + $this->assertInstanceOf(\stdClass::class, $foo); + $this->assertFalse(property_exists($foo, 'bar')); + } + + /** + * When performing an entity manager lazy service reset, the reset operations may re-use the container + * to create a "fresh" service: when doing so, it can happen that the "fresh" service is itself a proxy. + * + * Because of that, the proxy will be populated with a wrapped value that is itself a proxy: repeating + * the reset operation keeps increasing this nesting until the application eventually runs into stack + * overflow or memory overflow operations, which can happen for long-running processes that rely on + * services that are reset very often. + */ + public function testResetServiceWillNotNestFurtherLazyServicesWithinEachOther() + { + // This test scenario only applies to containers composed as a set of generated sources + $this->dumpLazyServiceProjectAsFilesServiceContainer(); + + /** @var ContainerInterface $container */ + $container = new \LazyServiceProjectAsFilesServiceContainer(); + + $registry = new TestManagerRegistry( + 'irrelevant', + [], + ['defaultManager' => 'foo'], + 'irrelevant', + 'defaultManager', + 'irrelevant' + ); + $registry->setTestContainer($container); + + $service = $container->get('foo'); + + self::assertInstanceOf(\stdClass::class, $service); + self::assertInstanceOf(LazyLoadingInterface::class, $service); + self::assertInstanceOf(ValueHolderInterface::class, $service); + self::assertFalse($service->isProxyInitialized()); + + $service->initializeProxy(); + + self::assertTrue($container->initialized('foo')); + self::assertTrue($service->isProxyInitialized()); + + $registry->resetManager(); + $service->initializeProxy(); + + $wrappedValue = $service->getWrappedValueHolderValue(); + self::assertInstanceOf(\stdClass::class, $wrappedValue); + self::assertNotInstanceOf(LazyLoadingInterface::class, $wrappedValue); + self::assertNotInstanceOf(ValueHolderInterface::class, $wrappedValue); + } + + private function dumpLazyServiceProjectAsFilesServiceContainer() + { + if (class_exists(\LazyServiceProjectAsFilesServiceContainer::class, false)) { + return; + } + + $container = new ContainerBuilder(); + + $container->register('foo', \stdClass::class) + ->setPublic(true) + ->setLazy(true); + $container->compile(); + + $fileSystem = new Filesystem(); + + $temporaryPath = $fileSystem->tempnam(sys_get_temp_dir(), 'symfonyManagerRegistryTest'); + $fileSystem->remove($temporaryPath); + $fileSystem->mkdir($temporaryPath); + + $dumper = new PhpDumper($container); + + $dumper->setProxyDumper(new ProxyDumper()); + $containerFiles = $dumper->dump([ + 'class' => 'LazyServiceProjectAsFilesServiceContainer', + 'as_files' => true, + ]); + + array_walk( + $containerFiles, + static function (string $containerSources, string $fileName) use ($temporaryPath): void { + (new Filesystem())->dumpFile($temporaryPath.'/'.$fileName, $containerSources); + } + ); + + require $temporaryPath.'/LazyServiceProjectAsFilesServiceContainer.php'; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php b/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php index e524ebceff0b8..1b32453d1a85a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php @@ -12,27 +12,29 @@ namespace Symfony\Bridge\Doctrine\Tests; use PHPUnit\Framework\TestCase; -use ProxyManager\Proxy\LazyLoadingInterface; -use ProxyManager\Proxy\ValueHolderInterface; -use Symfony\Bridge\Doctrine\ManagerRegistry; -use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; -use Symfony\Bridge\ProxyManager\Tests\LazyProxy\Dumper\PhpDumperTest; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\VarExporter\LazyObjectInterface; class ManagerRegistryTest extends TestCase { public static function setUpBeforeClass(): void { - $test = new PhpDumperTest(); - $test->testDumpContainerWithProxyServiceWillShareProxies(); + $container = new ContainerBuilder(); + + $container->register('foo', \stdClass::class)->setPublic(true); + $container->getDefinition('foo')->setLazy(true)->addTag('proxy', ['interface' => \stdClass::class]); + $container->compile(); + + $dumper = new PhpDumper($container); + eval('?>'.$dumper->dump(['class' => 'LazyServiceDoctrineBridgeContainer'])); } public function testResetService() { - $container = new \LazyServiceProjectServiceContainer(); + $container = new \LazyServiceDoctrineBridgeContainer(); $registry = new TestManagerRegistry('name', [], ['defaultManager' => 'foo'], 'defaultConnection', 'defaultManager', 'proxyInterfaceName'); $registry->setTestContainer($container); @@ -60,10 +62,10 @@ public function testResetService() public function testResetServiceWillNotNestFurtherLazyServicesWithinEachOther() { // This test scenario only applies to containers composed as a set of generated sources - $this->dumpLazyServiceProjectAsFilesServiceContainer(); + $this->dumpLazyServiceDoctrineBridgeContainerAsFiles(); /** @var ContainerInterface $container */ - $container = new \LazyServiceProjectAsFilesServiceContainer(); + $container = new \LazyServiceDoctrineBridgeContainerAsFiles(); $registry = new TestManagerRegistry( 'irrelevant', @@ -78,27 +80,25 @@ public function testResetServiceWillNotNestFurtherLazyServicesWithinEachOther() $service = $container->get('foo'); self::assertInstanceOf(\stdClass::class, $service); - self::assertInstanceOf(LazyLoadingInterface::class, $service); - self::assertInstanceOf(ValueHolderInterface::class, $service); - self::assertFalse($service->isProxyInitialized()); + self::assertInstanceOf(LazyObjectInterface::class, $service); + self::assertFalse($service->isLazyObjectInitialized()); - $service->initializeProxy(); + $service->initializeLazyObject(); self::assertTrue($container->initialized('foo')); - self::assertTrue($service->isProxyInitialized()); + self::assertTrue($service->isLazyObjectInitialized()); $registry->resetManager(); - $service->initializeProxy(); + $service->initializeLazyObject(); - $wrappedValue = $service->getWrappedValueHolderValue(); + $wrappedValue = $service->initializeLazyObject(); self::assertInstanceOf(\stdClass::class, $wrappedValue); - self::assertNotInstanceOf(LazyLoadingInterface::class, $wrappedValue); - self::assertNotInstanceOf(ValueHolderInterface::class, $wrappedValue); + self::assertNotInstanceOf(LazyObjectInterface::class, $wrappedValue); } - private function dumpLazyServiceProjectAsFilesServiceContainer() + private function dumpLazyServiceDoctrineBridgeContainerAsFiles() { - if (class_exists(\LazyServiceProjectAsFilesServiceContainer::class, false)) { + if (class_exists(\LazyServiceDoctrineBridgeContainerAsFiles::class, false)) { return; } @@ -106,7 +106,8 @@ private function dumpLazyServiceProjectAsFilesServiceContainer() $container->register('foo', \stdClass::class) ->setPublic(true) - ->setLazy(true); + ->setLazy(true) + ->addTag('proxy', ['interface' => \stdClass::class]); $container->compile(); $fileSystem = new Filesystem(); @@ -117,9 +118,8 @@ private function dumpLazyServiceProjectAsFilesServiceContainer() $dumper = new PhpDumper($container); - $dumper->setProxyDumper(new ProxyDumper()); $containerFiles = $dumper->dump([ - 'class' => 'LazyServiceProjectAsFilesServiceContainer', + 'class' => 'LazyServiceDoctrineBridgeContainerAsFiles', 'as_files' => true, ]); @@ -130,19 +130,6 @@ static function (string $containerSources, string $fileName) use ($temporaryPath } ); - require $temporaryPath.'/LazyServiceProjectAsFilesServiceContainer.php'; - } -} - -class TestManagerRegistry extends ManagerRegistry -{ - public function setTestContainer($container) - { - $this->container = $container; - } - - public function getAliasNamespace($alias): string - { - return 'Foo'; + require $temporaryPath.'/LazyServiceDoctrineBridgeContainerAsFiles.php'; } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index 631d8035bfaa1..1ddd8e5ea59f8 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -16,7 +16,6 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\ORMSetup; -use Doctrine\ORM\Tools\Setup; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy; @@ -34,9 +33,7 @@ class DoctrineExtractorTest extends TestCase { private function createExtractor() { - $config = class_exists(ORMSetup::class) - ? ORMSetup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true) - : Setup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true); + $config = ORMSetup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true); $entityManager = EntityManager::create(['driver' => 'pdo_sqlite'], $config); if (!DBALType::hasType('foo')) { diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriberTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php similarity index 88% rename from src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriberTest.php rename to src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php index 0a65b6e6bc720..6cec07f820f42 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaSubscriberTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php @@ -16,10 +16,10 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\Doctrine\SchemaListener\DoctrineDbalCacheAdapterSchemaSubscriber; +use Symfony\Bridge\Doctrine\SchemaListener\DoctrineDbalCacheAdapterSchemaListener; use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; -class DoctrineDbalCacheAdapterSchemaSubscriberTest extends TestCase +class DoctrineDbalCacheAdapterSchemaListenerTest extends TestCase { public function testPostGenerateSchema() { @@ -37,7 +37,7 @@ public function testPostGenerateSchema() ->method('configureSchema') ->with($schema, $dbalConnection); - $subscriber = new DoctrineDbalCacheAdapterSchemaSubscriber([$dbalAdapter]); + $subscriber = new DoctrineDbalCacheAdapterSchemaListener([$dbalAdapter]); $subscriber->postGenerateSchema($event); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/LockStoreSchemaListenerTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/LockStoreSchemaListenerTest.php new file mode 100644 index 0000000000000..d8d06a5fe0524 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/LockStoreSchemaListenerTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\SchemaListener; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\SchemaListener\LockStoreSchemaListener; +use Symfony\Component\Lock\Store\DoctrineDbalStore; + +class LockStoreSchemaListenerTest extends TestCase +{ + public function testPostGenerateSchemaLockPdo() + { + $schema = new Schema(); + $dbalConnection = $this->createMock(Connection::class); + $entityManager = $this->createMock(EntityManagerInterface::class); + $entityManager->expects($this->once()) + ->method('getConnection') + ->willReturn($dbalConnection); + $event = new GenerateSchemaEventArgs($entityManager, $schema); + + $lockStore = $this->createMock(DoctrineDbalStore::class); + $lockStore->expects($this->once()) + ->method('configureSchema') + ->with($schema, fn () => true); + + $subscriber = new LockStoreSchemaListener([$lockStore]); + $subscriber->postGenerateSchema($event); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaSubscriberTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php similarity index 89% rename from src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaSubscriberTest.php rename to src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php index ff4ab2c27a19c..6c58fec7ce939 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaSubscriberTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php @@ -19,11 +19,11 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\Doctrine\SchemaListener\MessengerTransportDoctrineSchemaSubscriber; +use Symfony\Bridge\Doctrine\SchemaListener\MessengerTransportDoctrineSchemaListener; use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport; use Symfony\Component\Messenger\Transport\TransportInterface; -class MessengerTransportDoctrineSchemaSubscriberTest extends TestCase +class MessengerTransportDoctrineSchemaListenerTest extends TestCase { public function testPostGenerateSchema() { @@ -43,7 +43,7 @@ public function testPostGenerateSchema() $otherTransport->expects($this->never()) ->method($this->anything()); - $subscriber = new MessengerTransportDoctrineSchemaSubscriber([$doctrineTransport, $otherTransport]); + $subscriber = new MessengerTransportDoctrineSchemaListener([$doctrineTransport, $otherTransport]); $subscriber->postGenerateSchema($event); } @@ -69,7 +69,8 @@ public function testOnSchemaCreateTable() ->with($table) ->willReturn('CREATE TABLE pizza (id integer NOT NULL)'); - $subscriber = new MessengerTransportDoctrineSchemaSubscriber([$otherTransport, $doctrineTransport]); + $subscriber = new MessengerTransportDoctrineSchemaListener([$otherTransport, $doctrineTransport]); + $subscriber->onSchemaCreateTable($event); $this->assertTrue($event->isDefaultPrevented()); $this->assertSame([ @@ -92,7 +93,8 @@ public function testOnSchemaCreateTableNoExtraSql() $platform->expects($this->never()) ->method('getCreateTableSQL'); - $subscriber = new MessengerTransportDoctrineSchemaSubscriber([$doctrineTransport]); + $subscriber = new MessengerTransportDoctrineSchemaListener([$doctrineTransport]); + $subscriber->onSchemaCreateTable($event); $this->assertFalse($event->isDefaultPrevented()); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaListenerTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaListenerTest.php new file mode 100644 index 0000000000000..fce89261082c7 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/PdoSessionHandlerSchemaListenerTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\SchemaListener; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\SchemaListener\PdoSessionHandlerSchemaListener; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + +class PdoSessionHandlerSchemaListenerTest extends TestCase +{ + public function testPostGenerateSchemaPdo() + { + $schema = new Schema(); + $dbalConnection = $this->createMock(Connection::class); + $entityManager = $this->createMock(EntityManagerInterface::class); + $entityManager->expects($this->once()) + ->method('getConnection') + ->willReturn($dbalConnection); + $event = new GenerateSchemaEventArgs($entityManager, $schema); + + $pdoSessionHandler = $this->createMock(PdoSessionHandler::class); + $pdoSessionHandler->expects($this->once()) + ->method('configureSchema') + ->with($schema, fn () => true); + + $subscriber = new PdoSessionHandlerSchemaListener($pdoSessionHandler); + $subscriber->postGenerateSchema($event); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/TestManagerRegistry.php b/src/Symfony/Bridge/Doctrine/Tests/TestManagerRegistry.php new file mode 100644 index 0000000000000..aae7ca27df6b2 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/TestManagerRegistry.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests; + +use Symfony\Bridge\Doctrine\ManagerRegistry; + +class TestManagerRegistry extends ManagerRegistry +{ + public function setTestContainer($container) + { + $this->container = $container; + } + + public function getAliasNamespace($alias): string + { + return 'Foo'; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php index 5e9048808023d..da1873cb91856 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php @@ -32,11 +32,11 @@ class UniqueEntity extends Constraint public $message = 'This value is already used.'; public $service = 'doctrine.orm.validator.unique'; - public $em = null; - public $entityClass = null; + public $em; + public $entityClass; public $repositoryMethod = 'findBy'; public $fields = []; - public $errorPath = null; + public $errorPath; public $ignoreNull = true; /** diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 71d73d88a4a44..67575134b660b 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -37,6 +37,8 @@ public function __construct(ManagerRegistry $registry) /** * @param object $entity * + * @return void + * * @throws UnexpectedTypeException * @throws ConstraintDefinitionException */ @@ -193,7 +195,7 @@ public function validate(mixed $entity, Constraint $constraint) ->addViolation(); } - private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, mixed $value) + private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, mixed $value): string { if (!\is_object($value) || $value instanceof \DateTimeInterface) { return $this->formatValue($value, self::PRETTY_DATE); diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php index c50c6e98120ab..bf8a5feb9f9e3 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php @@ -28,6 +28,9 @@ public function __construct(ManagerRegistry $registry) $this->registry = $registry; } + /** + * @return void + */ public function initialize(object $object) { $this->registry->getManagerForClass($object::class)?->initializeObject($object); diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 7430758321d3e..98721385f4e5e 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -19,59 +19,55 @@ "php": ">=8.1", "doctrine/event-manager": "^1.2|^2", "doctrine/persistence": "^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "require-dev": { - "symfony/stopwatch": "^5.4|^6.0", "symfony/cache": "^5.4|^6.0", "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", + "symfony/dependency-injection": "^6.2", + "symfony/doctrine-messenger": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", "symfony/form": "^5.4.21|^6.2.7", - "symfony/http-kernel": "^6.2", + "symfony/http-kernel": "^6.3", + "symfony/lock": "^6.3", "symfony/messenger": "^5.4|^6.0", - "symfony/doctrine-messenger": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", "symfony/property-info": "^5.4|^6.0", "symfony/proxy-manager-bridge": "^5.4|^6.0", "symfony/security-core": "^6.0", - "symfony/expression-language": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", "symfony/uid": "^5.4|^6.0", "symfony/validator": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", "symfony/var-dumper": "^5.4|^6.0", - "doctrine/annotations": "^1.10.4|^2", + "doctrine/annotations": "^1.13.1|^2", "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.7.4", + "doctrine/orm": "^2.12", "psr/log": "^1|^2|^3" }, "conflict": { + "doctrine/annotations": "<1.13.1", "doctrine/dbal": "<2.13.1", "doctrine/lexer": "<1.1", - "doctrine/orm": "<2.7.4", + "doctrine/orm": "<2.12", "phpunit/phpunit": "<5.4.3", "symfony/cache": "<5.4", - "symfony/dependency-injection": "<5.4", + "symfony/dependency-injection": "<6.2", "symfony/form": "<5.4.21|>=6,<6.2.7", + "symfony/http-foundation": "<6.3", "symfony/http-kernel": "<6.2", + "symfony/lock": "<6.3", "symfony/messenger": "<5.4", "symfony/property-info": "<5.4", "symfony/security-bundle": "<5.4", "symfony/security-core": "<6.0", "symfony/validator": "<5.4" }, - "suggest": { - "symfony/form": "", - "symfony/validator": "", - "symfony/property-info": "", - "doctrine/data-fixtures": "", - "doctrine/dbal": "", - "doctrine/orm": "" - }, "autoload": { "psr-4": { "Symfony\\Bridge\\Doctrine\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php index 3168e2598b5dc..5210e8eefafd5 100644 --- a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php +++ b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php @@ -50,6 +50,9 @@ public function isEnabled(): bool return parent::isEnabled(); } + /** + * @return void + */ protected function configure() { if (!class_exists(ConsoleFormatter::class)) { @@ -80,7 +83,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $filter = $input->getOption('filter'); if ($filter) { if (!class_exists(ExpressionLanguage::class)) { - throw new LogicException('Package "symfony/expression-language" is required to use the "filter" option.'); + throw new LogicException('Package "symfony/expression-language" is required to use the "filter" option. Try running "composer require symfony/expression-language".'); } $this->el = new ExpressionLanguage(); } @@ -145,7 +148,7 @@ private function getLogs($socket): iterable } } - private function displayLog(OutputInterface $output, int $clientId, array $record) + private function displayLog(OutputInterface $output, int $clientId, array $record): void { if (isset($record['log_id'])) { $clientId = unpack('H*', $record['log_id'])[1]; diff --git a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php index 31f2197b75afb..36de344954c1c 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php @@ -138,7 +138,7 @@ private function doFormat(array|LogRecord $record): mixed /** * @internal */ - public function echoLine(string $line, int $depth, string $indentPad) + public function echoLine(string $line, int $depth, string $indentPad): void { if (-1 !== $depth) { fwrite($this->outputBuffer, $line); diff --git a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php index c84db4d897df8..9088ddfe3309a 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php @@ -30,7 +30,7 @@ class ChromePhpHandler extends BaseChromePhpHandler /** * Adds the headers to the response once it's created. */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php index 064a0fca43a21..e387c869608e9 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php @@ -106,7 +106,7 @@ protected function getDefaultFormatter(): FormatterInterface return new LogstashFormatter('application'); } - private function sendToElasticsearch(array $records) + private function sendToElasticsearch(array $records): void { $formatter = $this->getForm 10000 atter(); @@ -164,7 +164,7 @@ public function __destruct() $this->wait(true); } - private function wait(bool $blocking) + private function wait(bool $blocking): void { foreach ($this->client->stream($this->responses, $blocking ? null : 0.0) as $response => $chunk) { try { diff --git a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php index 8713f6c68b3e3..36cb3ae821c6f 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php @@ -30,7 +30,7 @@ class FirePHPHandler extends BaseFirePHPHandler /** * Adds the headers to the response once it's created. */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php index e0862f8f45df3..718be59c13088 100644 --- a/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php @@ -77,6 +77,8 @@ private function doWrite(array|LogRecord $record): void * * @param string $content formatted email body to be sent * @param array $records the array of log records that formed this content + * + * @return void */ protected function send(string $content, array $records) { @@ -101,7 +103,6 @@ protected function getSubjectFormatter(string $format): FormatterInterface */ protected function buildMessage(string $content, array $records): Email { - $message = null; if ($this->messageTemplate instanceof Email) { $message = clone $this->messageTemplate; } elseif (\is_callable($this->messageTemplate)) { diff --git a/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php index 8576706080d44..6aedc7c36e267 100644 --- a/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php @@ -71,7 +71,7 @@ private function notify(array $records): void $this->notifier->send($notification, ...$this->notifier->getAdminRecipients()); } - private function getHighestRecord(array $records) + private function getHighestRecord(array $records): array { $highestRecord = null; foreach ($records as $record) { diff --git a/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php b/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php index 4ba90f7efbea3..4d566885c3e46 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php @@ -126,7 +126,7 @@ protected function getDefaultFormatter(): FormatterInterface return new VarDumperFormatter(); } - private static function nullErrorHandler() + private static function nullErrorHandler(): void { } diff --git a/src/Symfony/Bridge/Monolog/Logger.php b/src/Symfony/Bridge/Monolog/Logger.php index 4e2168a114866..367b3351ff102 100644 --- a/src/Symfony/Bridge/Monolog/Logger.php +++ b/src/Symfony/Bridge/Monolog/Logger.php @@ -40,6 +40,9 @@ public function countErrors(Request $request = null): int return 0; } + /** + * @return void + */ public function clear() { if ($logger = $this->getDebugLogger()) { @@ -56,6 +59,9 @@ public function reset(): void } } + /** + * @return void + */ public function removeDebugLogger() { foreach ($this->processors as $k => $processor) { diff --git a/src/Symfony/Bridge/Monolog/Processor/ConsoleCommandProcessor.php b/src/Symfony/Bridge/Monolog/Processor/ConsoleCommandProcessor.php index a5b26eacbae83..df2a7187201b4 100644 --- a/src/Symfony/Bridge/Monolog/Processor/ConsoleCommandProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/ConsoleCommandProcessor.php @@ -47,11 +47,17 @@ private function doInvoke(array|LogRecord $record): array|LogRecord return $record; } + /** + * @return void + */ public function reset() { unset($this->commandData); } + /** + * @return void + */ public function addCommandData(ConsoleEvent $event) { $this->commandData = [ diff --git a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php index 5c135d10065b0..c1ce2898dab37 100644 --- a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php @@ -35,7 +35,7 @@ private function doInvoke(array|LogRecord $record): array|LogRecord { $key = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_id($request) : ''; - $timestamp = $timestampRfc3339 = false; + $timestampRfc3339 = false; if ($record['datetime'] instanceof \DateTimeInterface) { $timestamp = $record['datetime']->getTimestamp(); $timestampRfc3339 = $record['datetime']->format(\DateTimeInterface::RFC3339_EXTENDED); @@ -90,12 +90,18 @@ public function countErrors(Request $request = null): int return array_sum($this->errorCount); } + /** + * @return void + */ public function clear() { $this->records = []; $this->errorCount = []; } + /** + * @return void + */ public function reset() { $this->clear(); diff --git a/src/Symfony/Bridge/Monolog/Processor/RouteProcessor.php b/src/Symfony/Bridge/Monolog/Processor/RouteProcessor.php index c9f28af084068..bec88e3ed0e14 100644 --- a/src/Symfony/Bridge/Monolog/Processor/RouteProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/RouteProcessor.php @@ -45,12 +45,12 @@ public function __invoke(array|LogRecord $record): array|LogRecord return $record; } - public function reset() + public function reset(): void { $this->routeData = []; } - public function addRouteData(RequestEvent $event) + public function addRouteData(RequestEvent $event): void { if ($event->isMainRequest()) { $this->reset(); @@ -73,7 +73,7 @@ public function addRouteData(RequestEvent $event) $this->routeData[spl_object_id($request)] = $currentRequestData; } - public function removeRouteData(FinishRequestEvent $event) + public function removeRouteData(FinishRequestEvent $event): void { $requestId = spl_object_id($event->getRequest()); unset($this->routeData[$requestId]); diff --git a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php index f72023cdfdac4..e34219b97cc4c 100644 --- a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php @@ -31,7 +31,7 @@ public function __construct(array $extraFields = null) parent::__construct([], $extraFields); } - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { if ($event->isMainRequest()) { $this->serverData = $event->getRequest()->server->all(); diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php index 43d5ef3cfab72..397e84259a706 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php @@ -25,7 +25,7 @@ class MailerHandlerTest extends TestCase { /** @var MockObject|MailerInterface */ - private $mailer = null; + private $mailer; protected function setUp(): void { @@ -39,9 +39,7 @@ public function testHandle() $this->mailer ->expects($this->once()) ->method('send') - ->with($this->callback(function (Email $email) { - return 'Alert: WARNING message' === $email->getSubject() && null === $email->getHtmlBody(); - })) + ->with($this->callback(fn (Email $email) => 'Alert: WARNING message' === $email->getSubject() && null === $email->getHtmlBody())) ; $handler->handle($this->getRecord(Logger::WARNING, 'message')); } @@ -53,9 +51,7 @@ public function testHandleBatch() $this->mailer ->expects($this->once()) ->method('send') - ->with($this->callback(function (Email $email) { - return 'Alert: ERROR error' === $email->getSubject() && null === $email->getHtmlBody(); - })) + ->with($this->callback(fn (Email $email) => 'Alert: ERROR error' === $email->getSubject() && null === $email->getHtmlBody())) ; $handler->handleBatch($this->getMultipleRecords()); } @@ -86,9 +82,7 @@ public function testHtmlContent() $this->mailer ->expects($this->once()) ->method('send') - ->with($this->callback(function (Email $email) { - return 'Alert: WARNING message' === $email->getSubject() && null === $email->getTextBody(); - })) + ->with($this->callback(fn (Email $email) => 'Alert: WARNING message' === $email->getSubject() && null === $email->getTextBody())) ; $handler->handle($this->getRecord(Logger::WARNING, 'message')); } diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index 025d54a48398d..ca8b92ae601f5 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=8.1", "monolog/monolog": "^1.25.1|^2|^3", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/http-kernel": "^5.4|^6.0" }, "require-dev": { @@ -35,11 +35,6 @@ "symfony/http-foundation": "<5.4", "symfony/security-core": "<6.0" }, - "suggest": { - "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.", - "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings.", - "symfony/var-dumper": "For using the debugging handlers like the console handler or the log server handler." - }, "autoload": { "psr-4": { "Symfony\\Bridge\\Monolog\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index e7e3e29862bd4..9c664176565b7 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add support for mocking the `enum_exists` function + 6.2 --- diff --git a/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php b/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php index 70fdb9f9631ad..bbf9b76a0c3f3 100644 --- a/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php +++ b/src/Symfony/Bridge/PhpUnit/ClassExistsMock.php @@ -18,16 +18,36 @@ class ClassExistsMock { private static $classes = []; + private static $enums = []; + /** * Configures the classes to be checked upon existence. * - * @param array $classes Mocked class names as keys (case sensitive, without leading root namespace slash) and booleans as values + * @param array $classes Mocked class names as keys (case-sensitive, without leading root namespace slash) and booleans as values + * + * @return void */ public static function withMockedClasses(array $classes) { self::$classes = $classes; } + /** + * Configures the enums to be checked upon existence. + * + * @param array $enums Mocked enums names as keys (case-sensitive, without leading root namespace slash) and booleans as values + * + * @return void + */ + public static function withMockedEnums(array $enums) + { + self::$enums = $enums; + self::$classes += $enums; + } + + /** + * @return bool + */ public static function class_exists($name, $autoload = true) { $name = ltrim($name, '\\'); @@ -35,6 +55,9 @@ public static function class_exists($name, $autoload = true) return isset(self::$classes[$name]) ? (bool) self::$classes[$name] : \class_exists($name, $autoload); } + /** + * @return bool + */ public static function interface_exists($name, $autoload = true) { $name = ltrim($name, '\\'); @@ -42,6 +65,9 @@ public static function interface_exists($name, $autoload = true) return isset(self::$classes[$name]) ? (bool) self::$classes[$name] : \interface_exists($name, $autoload); } + /** + * @return bool + */ public static function trait_exists($name, $autoload = true) { $name = ltrim($name, '\\'); @@ -49,6 +75,19 @@ public static function trait_exists($name, $autoload = true) return isset(self::$classes[$name]) ? (bool) self::$classes[$name] : \trait_exists($name, $autoload); } + /** + * @return bool + */ + public static function enum_exists($name, $autoload = true) + { + $name = ltrim($name, '\\'); + + return isset(self::$enums[$name]) ? (bool) self::$enums[$name] : \enum_exists($name, $autoload); + } + + /** + * @return void + */ public static function register($class) { $self = static::class; @@ -61,7 +100,7 @@ public static function register($class) $mockedNs[] = substr($class, 6, strrpos($class, '\\') - 6); } foreach ($mockedNs as $ns) { - foreach (['class', 'interface', 'trait'] as $type) { + foreach (['class', 'interface', 'trait', 'enum'] as $type) { if (\function_exists($ns.'\\'.$type.'_exists')) { continue; } diff --git a/src/Symfony/Bridge/PhpUnit/ClockMock.php b/src/Symfony/Bridge/PhpUnit/ClockMock.php index 7ec22ccd85cb3..64a7ac8fa14d7 100644 --- a/src/Symfony/Bridge/PhpUnit/ClockMock.php +++ b/src/Symfony/Bridge/PhpUnit/ClockMock.php @@ -19,6 +19,9 @@ class ClockMock { private static $now; + /** + * @return bool|null + */ public static function withClockMock($enable = null) { if (null === $enable) { @@ -30,6 +33,9 @@ public static function withClockMock($enable = null) return null; } + /** + * @return int + */ public static function time() { if (null === self::$now) { @@ -39,6 +45,9 @@ public static function time() return (int) self::$now; } + /** + * @return int + */ public static function sleep($s) { if (null === self::$now) { @@ -50,6 +59,9 @@ public static function sleep($s) return 0; } + /** + * @return void + */ public static function usleep($us) { if (null === self::$now) { @@ -72,6 +84,9 @@ public static function microtime($asFloat = false) return sprintf('%0.6f00 %d', self::$now - (int) self::$now, (int) self::$now); } + /** + * @return string + */ public static function date($format, $timestamp = null) { if (null === $timestamp) { @@ -81,6 +96,9 @@ public static function date($format, $timestamp = null) return \date($format, $timestamp); } + /** + * @return string + */ public static function gmdate($format, $timestamp = null) { if (null === $timestamp) { @@ -90,6 +108,9 @@ public static function gmdate($format, $timestamp = null) return \gmdate($format, $timestamp); } + /** + * @return array|int|float + */ public static function hrtime($asNumber = false) { $ns = (self::$now - (int) self::$now) * 1000000000; @@ -103,6 +124,9 @@ public static function hrtime($asNumber = false) return [(int) self::$now, (int) $ns]; } + /** + * @return void + */ public static function register($class) { $self = static::class; diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 49e9f5ceb5c58..f3a78b97f45d0 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -360,7 +360,7 @@ private function displayDeprecations($groups, $configuration) } } - private static function getPhpUnitErrorHandler() + private static function getPhpUnitErrorHandler(): callable { if (!$eh = self::$errorHandler) { if (class_exists(Handler::class)) { diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php index 45d1ed7ce9940..fd2730b0f1d5a 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php @@ -59,7 +59,7 @@ class Configuration /** * @var string|null */ - private $logFile = null; + private $logFile; /** * @param int[] $thresholds A hash associating groups to thresholds @@ -154,20 +154,15 @@ private function __construct(array $thresholds = [], $regex = '', $verboseOutput $this->logFile = $logFile; } - /** - * @return bool - */ - public function isEnabled() + public function isEnabled(): bool { return $this->enabled; } /** * @param DeprecationGroup[] $deprecationGroups - * - * @return bool */ - public function tolerates(array $deprecationGroups) + public function tolerates(array $deprecationGroups): bool { $grandTotal = 0; @@ -229,10 +224,7 @@ public function toleratesForGroup(string $groupName, array $deprecationGroups): return true; } - /** - * @return bool - */ - public function isBaselineDeprecation(Deprecation $deprecation) + public function isBaselineDeprecation(Deprecation $deprecation): bool { if ($deprecation->isLegacy()) { return false; @@ -260,20 +252,17 @@ public function isBaselineDeprecation(Deprecation $deprecation) return $result; } - /** - * @return bool - */ - public function isGeneratingBaseline() + public function isGeneratingBaseline(): bool { return $this->generateBaseline; } - public function getBaselineFile() + public function getBaselineFile(): string { return $this->baselineFile; } - public function writeBaseline() + public function writeBaseline(): void { $map = []; foreach ($this->baselineDeprecations as $location => $messages) { @@ -290,36 +279,28 @@ public function writeBaseline() /** * @param string $message - * - * @return bool */ - public function shouldDisplayStackTrace($message) + public function shouldDisplayStackTrace($message): bool { return '' !== $this->regex && preg_match($this->regex, $message); } - /** - * @return bool - */ - public function isInRegexMode() + public function isInRegexMode(): bool { return '' !== $this->regex; } - /** - * @return bool - */ - public function verboseOutput($group) + public function verboseOutput($group): bool { return $this->verboseOutput[$group]; } - public function shouldWriteToLogFile() + public function shouldWriteToLogFile(): bool { return null !== $this->logFile; } - public function getLogFile() + public function getLogFile(): ?string { return $this->logFile; } @@ -327,10 +308,8 @@ public function getLogFile() /** * @param string $serializedConfiguration an encoded string, for instance * max[total]=1234&max[indirect]=42 - * - * @return self */ - public static function fromUrlEncodedString($serializedConfiguration) + public static function fromUrlEncodedString($serializedConfiguration): self { parse_str($serializedConfiguration, $normalizedConfiguration); foreach (array_keys($normalizedConfiguration) as $key) { @@ -376,10 +355,7 @@ public static function fromUrlEncodedString($serializedConfiguration) ); } - /** - * @return self - */ - public static function inDisabledMode() + public static function inDisabledMode(): self { $configuration = new self(); $configuration->enabled = false; @@ -387,18 +363,12 @@ public static function inDisabledMode() return $configuration; } - /** - * @return self - */ - public static function inStrictMode() + public static function inStrictMode(): self { return new self(['total' => 0]); } - /** - * @return self - */ - public static function inWeakMode() + public static function inWeakMode(): self { $verboseOutput = []; foreach (['unsilenced', 'direct', 'indirect', 'self', 'other'] as $group) { @@ -408,18 +378,12 @@ public static function inWeakMode() return new self([], '', $verboseOutput); } - /** - * @return self - */ - public static function fromNumber($upperBound) + public static function fromNumber($upperBound): self { return new self(['total' => $upperBound]); } - /** - * @return self - */ - public static function fromRegex($regex) + public static function fromRegex($regex): self { return new self([], $regex); } diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index e014104d3f012..e18d9b1a289f2 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -356,7 +356,7 @@ private static function getVendors() return self::$vendors; } - private static function addSourcePathsFromPrefixes(array $prefixesByNamespace, array $paths) + private static function addSourcePathsFromPrefixes(array $prefixesByNamespace, array $paths): array { foreach ($prefixesByNamespace as $prefixes) { foreach ($prefixes as $prefix) { diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php index 6ad2b84ea3fd6..23b95e19fc3d4 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php @@ -50,20 +50,18 @@ public function addNotice() /** * @param string $message - * - * @return DeprecationNotice */ - private function deprecationNotice($message) + private function deprecationNotice($message): DeprecationNotice { return $this->deprecationNotices[$message] ?? $this->deprecationNotices[$message] = new DeprecationNotice(); } - public function count() + public function count(): int { return $this->count; } - public function notices() + public function notices(): array { return $this->deprecationNotices; } diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationNotice.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationNotice.php index 854bbd4d26333..7cb77b8927498 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationNotice.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationNotice.php @@ -37,12 +37,12 @@ public function addProceduralOccurrence() ++$this->count; } - public function getCountsByCaller() + public function getCountsByCaller(): array { return $this->countsByCaller; } - public function count() + public function count(): int { return $this->count; } diff --git a/src/Symfony/Bridge/PhpUnit/DnsMock.php b/src/Symfony/Bridge/PhpUnit/DnsMock.php index 642da0a6dfcde..f0145e7d8a7f6 100644 --- a/src/Symfony/Bridge/PhpUnit/DnsMock.php +++ b/src/Symfony/Bridge/PhpUnit/DnsMock.php @@ -36,12 +36,17 @@ class DnsMock * Configures the mock values for DNS queries. * * @param array $hosts Mocked hosts as keys, arrays of DNS records as returned by dns_get_record() as values + * + * @return void */ public static function withMockedHosts(array $hosts) { self::$hosts = $hosts; } + /** + * @return bool + */ public static function checkdnsrr($hostname, $type = 'MX') { if (!self::$hosts) { @@ -63,6 +68,9 @@ public static function checkdnsrr($hostname, $type = 'MX') return false; } + /** + * @return bool + */ public static function getmxrr($hostname, &$mxhosts, &$weight = null) { if (!self::$hosts) { @@ -161,6 +169,9 @@ public static function dns_get_record($hostname, $type = \DNS_ANY, &$authns = nu return $records; } + /** + * @return void + */ public static function register($class) { $self = static::class; diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitBeforeV8_4.php b/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitBeforeV8_4.php index 03368b5597fbe..d60587eca9b03 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitBeforeV8_4.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitBeforeV8_4.php @@ -18,10 +18,8 @@ trait ExpectDeprecationTraitBeforeV8_4 { /** * @param string $message - * - * @return void */ - protected function expectDeprecation($message) + protected function expectDeprecation($message): void { // Expected deprecations set by isolated tests need to be written to a file // so that the test running process can take account of them. diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index 015bc881ae573..62e57b8e9707c 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -107,13 +107,13 @@ public function __destruct() } } - public function globalListenerDisabled() + public function globalListenerDisabled(): void { self::$globallyEnabled = false; $this->state = -1; } - public function startTestSuite($suite) + public function startTestSuite($suite): void { $suiteName = $suite->getName(); @@ -188,14 +188,14 @@ public function startTestSuite($suite) } } - public function addSkippedTest($test, \Exception $e, $time) + public function addSkippedTest($test, \Exception $e, $time): void { if (0 < $this->state) { $this->isSkipped[\get_class($test)][$test->getName()] = 1; } } - public function startTest($test) + public function startTest($test): void { if (-2 < $this->state && $test instanceof TestCase) { // This event is triggered before the test is re-run in isolation @@ -224,7 +224,7 @@ public function startTest($test) $annotations = Test::parseTestMethodAnnotations(\get_class($test), $test->getName(false)); if (isset($annotations['class']['expectedDeprecation'])) { - $test->getTestResultObject()->addError($test, new AssertionFailedError('`@expectedDeprecation` annotations are not allowed at the class level.'), 0); + $test->getTestResultObject()->addError($test, new AssertionFailedError('"@expectedDeprecation" annotations are not allowed at the class level.'), 0); } if (isset($annotations['method']['expectedDeprecation']) || $this->checkNumAssertions = method_exists($test, 'expectDeprecation') && (new \ReflectionMethod($test, 'expectDeprecation'))->getFileName() === (new \ReflectionMethod(ExpectDeprecationTrait::class, 'expectDeprecation'))->getFileName()) { if (isset($annotations['method']['expectedDeprecation'])) { @@ -242,7 +242,7 @@ public function startTest($test) } } - public function endTest($test, $time) + public function endTest($test, $time): void { if ($file = getenv('SYMFONY_EXPECTED_DEPRECATIONS_SERIALIZE')) { putenv('SYMFONY_EXPECTED_DEPRECATIONS_SERIALIZE'); @@ -297,7 +297,7 @@ public function endTest($test, $time) restore_error_handler(); if (!\in_array('legacy', $groups, true)) { - $test->getTestResultObject()->addError($test, new AssertionFailedError('Only tests with the `@group legacy` annotation can expect a deprecation.'), 0); + $test->getTestResultObject()->addError($test, new AssertionFailedError('Only tests with the "@group legacy" annotation can expect a deprecation.'), 0); } elseif (!\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE, BaseTestRunner::STATUS_FAILURE, BaseTestRunner::STATUS_ERROR], true)) { try { $prefix = "@expectedDeprecation:\n"; @@ -338,15 +338,10 @@ public static function handleError($type, $msg, $file, $line, $context = []) } self::$gatheredDeprecations[] = $msg; - return null; + return true; } - /** - * @param TestCase $test - * - * @return bool - */ - private function willBeIsolated($test) + private function willBeIsolated(TestCase $test): bool { if ($test->isInIsolation()) { return false; @@ -355,6 +350,6 @@ private function willBeIsolated($test) $r = new \ReflectionProperty($test, 'runTestInSeparateProcess'); $r->setAccessible(true); - return $r->getValue($test); + return $r->getValue($test) ?? false; } } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/ClassExistsMockTest.php b/src/Symfony/Bridge/PhpUnit/Tests/ClassExistsMockTest.php index 3e3d5771b1b10..58c01646a8a61 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/ClassExistsMockTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/ClassExistsMockTest.php @@ -31,6 +31,10 @@ protected function setUp(): void ExistingTrait::class => false, 'NonExistingTrait' => true, ]); + + ClassExistsMock::withMockedEnums([ + 'NonExistingEnum' => true, + ]); } public function testClassExists() @@ -53,6 +57,26 @@ public function testClassExists() $this->assertFalse(class_exists('\\NonExistingClassReal', false)); } + public function testEnumExistsOnClasses() + { + $this->assertFalse(enum_exists(ExistingClass::class)); + $this->assertFalse(enum_exists(ExistingClass::class, false)); + $this->assertFalse(enum_exists('\\'.ExistingClass::class)); + $this->assertFalse(enum_exists('\\'.ExistingClass::class, false)); + $this->assertFalse(enum_exists('NonExistingClass')); + $this->assertFalse(enum_exists('NonExistingClass', false)); + $this->assertFalse(enum_exists('\\NonExistingClass')); + $this->assertFalse(enum_exists('\\NonExistingClass', false)); + $this->assertFalse(enum_exists(ExistingClassReal::class)); + $this->assertFalse(enum_exists(ExistingClassReal::class, false)); + $this->assertFalse(enum_exists('\\'.ExistingClassReal::class)); + $this->assertFalse(enum_exists('\\'.ExistingClassReal::class, false)); + $this->assertFalse(enum_exists('NonExistingClassReal')); + $this->assertFalse(enum_exists('NonExistingClassReal', false)); + $this->assertFalse(enum_exists('\\NonExistingClassReal')); + $this->assertFalse(enum_exists('\\NonExistingClassReal', false)); + } + public function testInterfaceExists() { $this->assertFalse(interface_exists(ExistingInterface::class)); @@ -92,6 +116,28 @@ public function testTraitExists() $this->assertFalse(trait_exists('\\NonExistingTraitReal')); $this->assertFalse(trait_exists('\\NonExistingTraitReal', false)); } + + public function testEnumExists() + { + $this->assertTrue(enum_exists('NonExistingEnum')); + $this->assertTrue(enum_exists('NonExistingEnum', false)); + $this->assertTrue(enum_exists('\\NonExistingEnum')); + $this->assertTrue(enum_exists('\\NonExistingEnum', false)); + $this->assertFalse(enum_exists('NonExistingClassReal')); + $this->assertFalse(enum_exists('NonExistingClassReal', false)); + $this->assertFalse(enum_exists('\\NonExistingEnumReal')); + $this->assertFalse(enum_exists('\\NonExistingEnumReal', false)); + } + + public function testClassExistsOnEnums() + { + $this->assertTrue(class_exists('NonExistingEnum')); + $this->assertTrue(class_exists('NonExistingEnum', false)); + $this->assertTrue(class_exists('\\NonExistingEnum')); + $this->assertTrue(class_exists('\\NonExistingEnum', false)); + $this->assertFalse(class_exists('\\NonExistingEnumReal')); + $this->assertFalse(class_exists('\\NonExistingEnumReal', false)); + } } class ExistingClass diff --git a/src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php b/src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php new file mode 100644 index 0000000000000..21cdd290400ca --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/EnumExistsMockTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ClassExistsMock; +use Symfony\Bridge\PhpUnit\Tests\Fixtures\ExistingEnum; +use Symfony\Bridge\PhpUnit\Tests\Fixtures\ExistingEnumReal; + +/** + * @requires PHP 8.1 + */ +class EnumExistsMockTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + // Require the fixture file to allow PHP to be fully aware of the enum existence + require __DIR__.'/Fixtures/ExistingEnumReal.php'; + + ClassExistsMock::register(__CLASS__); + } + + protected function setUp(): void + { + ClassExistsMock::withMockedEnums([ + ExistingEnum::class => false, + 'NonExistingEnum' => true, + ]); + } + + public static function tearDownAfterClass(): void + { + ClassExistsMock::withMockedEnums([]); + } + + public function testClassExists() + { + $this->assertFalse(class_exists(ExistingEnum::class)); + $this->assertFalse(class_exists(ExistingEnum::class, false)); + $this->assertFalse(class_exists('\\'.ExistingEnum::class)); + $this->assertFalse(class_exists('\\'.ExistingEnum::class, false)); + $this->assertTrue(class_exists('NonExistingEnum')); + $this->assertTrue(class_exists('NonExistingEnum', false)); + $this->assertTrue(class_exists('\\NonExistingEnum')); + $this->assertTrue(class_exists('\\NonExistingEnum', false)); + $this->assertTrue(class_exists(ExistingEnumReal::class)); + $this->assertTrue(class_exists(ExistingEnumReal::class, false)); + $this->assertTrue(class_exists('\\'.ExistingEnumReal::class)); + $this->assertTrue(class_exists('\\'.ExistingEnumReal::class, false)); + $this->assertFalse(class_exists('\\NonExistingEnumReal')); + $this->assertFalse(class_exists('\\NonExistingEnumReal', false)); + } + + public function testEnumExists() + { + $this->assertFalse(enum_exists(ExistingEnum::class)); + $this->assertFalse(enum_exists(ExistingEnum::class, false)); + $this->assertFalse(enum_exists('\\'.ExistingEnum::class)); + $this->assertFalse(enum_exists('\\'.ExistingEnum::class, false)); + $this->assertTrue(enum_exists('NonExistingEnum')); + $this->assertTrue(enum_exists('NonExistingEnum', false)); + $this->assertTrue(enum_exists('\\NonExistingEnum')); + $this->assertTrue(enum_exists('\\NonExistingEnum', false)); + $this->assertTrue(enum_exists(ExistingEnumReal::class)); + $this->assertTrue(enum_exists(ExistingEnumReal::class, false)); + $this->assertTrue(enum_exists('\\'.ExistingEnumReal::class)); + $this->assertTrue(enum_exists('\\'.ExistingEnumReal::class, false)); + $this->assertFalse(enum_exists('NonExistingClassReal')); + $this->assertFalse(enum_exists('NonExistingClassReal', false)); + $this->assertFalse(enum_exists('\\NonExistingEnumReal')); + $this->assertFalse(enum_exists('\\NonExistingEnumReal', false)); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnum.php b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnum.php new file mode 100644 index 0000000000000..039e293b0c0d7 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnum.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests\Fixtures; + +enum ExistingEnum +{ +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnumReal.php b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnumReal.php new file mode 100644 index 0000000000000..028090638d060 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/ExistingEnumReal.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests\Fixtures; + +enum ExistingEnumReal +{ +} diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index b0ccda04315f1..77c408dc14ff2 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -21,11 +21,9 @@ "php": ">=7.1.3" }, "require-dev": { - "symfony/deprecation-contracts": "^2.1|^3.0", - "symfony/error-handler": "^5.4|^6.0" - }, - "suggest": { - "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/polyfill-php81": "^1.27" }, "conflict": { "phpunit/phpunit": "<7.5|9.1.2" diff --git a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md index 3435a4a186494..5ba6cdaf730a1 100644 --- a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md +++ b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate the bridge + 4.2.0 ----- diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php index 59fcdc022efce..590dc2108e372 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -21,10 +21,14 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; +trigger_deprecation('symfony/proxy-manager-bridge', '6.3', 'The "symfony/proxy-manager-bridge" package is deprecated and can be removed from your dependencies.'); + /** * Runtime lazy loading proxy generator. * * @author Marco Pivetta + * + * @deprecated since Symfony 6.3 */ class RuntimeInstantiator implements InstantiatorInterface { diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index 82ff95dc4cc93..fd610860f534f 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -17,11 +17,15 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; +trigger_deprecation('symfony/proxy-manager-bridge', '6.3', 'The "symfony/proxy-manager-bridge" package is deprecated and can be removed from your dependencies.'); + /** * Generates dumped PHP code of proxies via reflection. * * @author Marco Pivetta * + * @deprecated since Symfony 6.3 + * * @final */ class ProxyDumper implements DumperInterface @@ -49,7 +53,7 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ $instantiation = 'return'; if ($definition->isShared()) { - $instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); + $instantiation .= sprintf(' $container->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); } $proxifiedClass = new \ReflectionClass($this->proxyGenerator->getProxifiedClass($definition)); @@ -57,8 +61,9 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ return <<createProxy('$proxyClass', function () { - return \\$proxyClass::staticProxyConstructor(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) { + $instantiation \$container->createProxy('$proxyClass', static function () use (\$containerRef) { + return \\$proxyClass::staticProxyConstructor(static function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$containerRef) { + \$container = \$containerRef->get(); \$wrappedInstance = $factoryCode; \$proxy->setProxyInitializer(null); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php index 095a8631784a9..dbe5795cb3447 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php @@ -23,6 +23,8 @@ * with the ProxyManager bridge. * * @author Marco Pivetta + * + * @group legacy */ class ContainerBuilderTest extends TestCase { diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php index aedfff33c56c5..32992796c0ebf 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php @@ -22,6 +22,8 @@ * with the ProxyManager bridge. * * @author Marco Pivetta + * + * @group legacy */ class PhpDumperTest extends TestCase { diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt index 825c1051ca38f..84995384157b6 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt @@ -3,12 +3,15 @@ use %a class LazyServiceProjectServiceContainer extends Container {%a - protected function getFooService($lazyLoad = true) + protected static function getFooService($container, $lazyLoad = true) { + $containerRef = $container->ref; + if (true === $lazyLoad) { - return $this->services['foo'] = $this->createProxy('stdClass_%s', function () { - return %S\stdClass_%s(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) { - $wrappedInstance = $this->getFooService(false); + return $container->services['foo'] = $container->createProxy('stdClass_%s', static function () use ($containerRef) { + return %S\stdClass_%s(static function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($containerRef) { + $container = $containerRef->get(); + $wrappedInstance = self::getFooService($containerRef->get(), false); $proxy->setProxyInitializer(null); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php index fc04526ced826..15190df1d308b 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php @@ -22,6 +22,8 @@ * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator}. * * @author Marco Pivetta + * + * @group legacy */ class RuntimeInstantiatorTest extends TestCase { @@ -40,9 +42,7 @@ public function testInstantiateProxy() $instance = new \stdClass(); $container = $this->createMock(ContainerInterface::class); $definition = new Definition('stdClass'); - $instantiator = function () use ($instance) { - return $instance; - }; + $instantiator = fn () => $instance; /* @var $proxy LazyLoadingInterface|ValueHolderInterface */ $proxy = $this->instantiator->instantiateProxy($container, $definition, 'foo', $instantiator); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php index c06cb534b6e79..12a7de3483761 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php @@ -7,10 +7,14 @@ public function getFooService($lazyLoad = true) { + $container = $this; + $containerRef = \WeakReference::create($this); + if (true === $lazyLoad) { - return $this->privates['foo'] = $this->createProxy('SunnyInterface_1eff735', function () { - return \SunnyInterface_1eff735::staticProxyConstructor(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) { - $wrappedInstance = $this->getFooService(false); + return $container->privates['foo'] = $container->createProxy('SunnyInterface_1eff735', static function () use ($containerRef) { + return \SunnyInterface_1eff735::staticProxyConstructor(static function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($containerRef) { + $container = $containerRef->get(); + $wrappedInstance = $container->getFooService(false); $proxy->setProxyInitializer(null); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index b9ad7ae1b528a..ea376464fc925 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -20,6 +20,8 @@ * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper}. * * @author Marco Pivetta + * + * @group legacy */ class ProxyDumperTest extends TestCase { @@ -70,10 +72,10 @@ public function testGetProxyFactoryCode() $definition->setLazy(true); - $code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFoo2Service(false)'); + $code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$container->getFoo2Service(false)'); $this->assertStringMatchesFormat( - '%A$wrappedInstance = $this->getFoo2Service(false);%w$proxy->setProxyInitializer(null);%A', + '%A$wrappedInstance = $container->getFoo2Service(false);%w$proxy->setProxyInitializer(null);%A', $code ); } @@ -85,9 +87,9 @@ public function testCorrectAssigning(Definition $definition, $access) { $definition->setLazy(true); - $code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFoo2Service(false)'); + $code = $this->dumper->getProxyFactoryCode($definition, 'foo', '$container->getFoo2Service(false)'); - $this->assertStringMatchesFormat('%A$this->'.$access.'[\'foo\'] = %A', $code); + $this->assertStringMatchesFormat('%A$container->'.$access.'[\'foo\'] = %A', $code); } public static function getPrivatePublicDefinitions() @@ -116,7 +118,7 @@ public function testGetProxyFactoryCodeForInterface() $definition->addTag('proxy', ['interface' => SunnyInterface::class]); $implem = "dumper->getProxyCode($definition); - $factory = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFooService(false)'); + $factory = $this->dumper->getProxyFactoryCode($definition, 'foo', '$container->getFooService(false)'); $factory = <<=8.1", "friendsofphp/proxy-manager-lts": "^1.0.2", - "symfony/dependency-injection": "^6.2" + "symfony/dependency-injection": "^6.3", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { "symfony/config": "^6.1" diff --git a/src/Symfony/Bridge/Twig/AppVariable.php b/src/Symfony/Bridge/Twig/AppVariable.php index 6cdbaa8370811..8bfaa0a22c601 100644 --- a/src/Symfony/Bridge/Twig/AppVariable.php +++ b/src/Symfony/Bridge/Twig/AppVariable.php @@ -17,6 +17,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Translation\LocaleSwitcher; /** * Exposes some Symfony parameters and services as an "app" global variable. @@ -29,27 +30,45 @@ class AppVariable private RequestStack $requestStack; private string $environment; private bool $debug; + private LocaleSwitcher $localeSwitcher; + /** + * @return void + */ public function setTokenStorage(TokenStorageInterface $tokenStorage) { $this->tokenStorage = $tokenStorage; } + /** + * @return void + */ public function setRequestStack(RequestStack $requestStack) { $this->requestStack = $requestStack; } + /** + * @return void + */ public function setEnvironment(string $environment) { $this->environment = $environment; } + /** + * @return void + */ public function setDebug(bool $debug) { $this->debug = $debug; } + public function setLocaleSwitcher(LocaleSwitcher $localeSwitcher): void + { + $this->localeSwitcher = $localeSwitcher; + } + /** * Returns the current token. * @@ -127,6 +146,15 @@ public function getDebug(): bool return $this->debug; } + public function getLocale(): string + { + if (!isset($this->localeSwitcher)) { + throw new \RuntimeException('The "app.locale" variable is not available.'); + } + + return $this->localeSwitcher->getLocale(); + } + /** * Returns some or all the existing flash messages: * * getFlashes() returns all the flash messages diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 8edc9b80190b4..9613d9a3fd6e0 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add `AppVariable::getLocale()` to retrieve the current locale when using the `LocaleSwitcher` + 6.2 --- diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index 6fae02cb9bc91..43e4d9c9f12c6 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -59,13 +59,16 @@ public function __construct(Environment $twig, string $projectDir = null, array $this->fileLinkFormatter = $fileLinkFormatter; } + /** + * @return void + */ protected function configure() { $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The template name'), new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'text'), ]) ->setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, @@ -104,7 +107,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int match ($input->getOption('format')) { 'text' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), 'json' => $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter), - default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; return 0; @@ -117,11 +120,11 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues(['text', 'json']); + $suggestions->suggestValues($this->getAvailableFormatOptions()); } } - private function displayPathsText(SymfonyStyle $io, string $name) + private function displayPathsText(SymfonyStyle $io, string $name): void { $file = new \ArrayIterator($this->findTemplateFiles($name)); $paths = $this->getLoaderPaths($name); @@ -162,9 +165,7 @@ private function displayPathsText(SymfonyStyle $io, string $name) [$namespace, $shortname] = $this->parseTemplateName($name); $alternatives = $this->findAlternatives($shortname, $shortnames); if (FilesystemLoader::MAIN_NAMESPACE !== $namespace) { - $alternatives = array_map(function ($shortname) use ($namespace) { - return '@'.$namespace.'/'.$shortname; - }, $alternatives); + $alternatives = array_map(fn ($shortname) => '@'.$namespace.'/'.$shortname, $alternatives); } } @@ -198,7 +199,7 @@ private function displayPathsText(SymfonyStyle $io, string $name) } } - private function displayPathsJson(SymfonyStyle $io, string $name) + private function displayPathsJson(SymfonyStyle $io, string $name): void { $files = $this->findTemplateFiles($name); $paths = $this->getLoaderPaths($name); @@ -216,7 +217,7 @@ private function displayPathsJson(SymfonyStyle $io, string $name) $io->writeln(json_encode($data)); } - private function displayGeneralText(SymfonyStyle $io, string $filter = null) + private function displayGeneralText(SymfonyStyle $io, string $filter = null): void { $decorated = $io->isDecorated(); $types = ['functions', 'filters', 'tests', 'globals']; @@ -250,7 +251,7 @@ private function displayGeneralText(SymfonyStyle $io, string $filter = null) } } - private function displayGeneralJson(SymfonyStyle $io, ?string $filter) + private function displayGeneralJson(SymfonyStyle $io, ?string $filter): void { $decorated = $io->isDecorated(); $types = ['functions', 'filters', 'tests', 'globals']; @@ -304,7 +305,7 @@ private function getLoaderPaths(string $name = null): array return $loaderPaths; } - private function getMetadata(string $type, mixed $entity) + private function getMetadata(string $type, mixed $entity): mixed { if ('globals' === $type) { return $entity; @@ -543,7 +544,7 @@ private function findAlternatives(string $name, array $collection): array } $threshold = 1e3; - $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold); ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); return array_keys($alternatives); @@ -595,4 +596,9 @@ private function getFileLink(string $absolutePath): string return (string) $this->fileLinkFormatter->format($absolutePath, 1); } + + private function getAvailableFormatOptions(): array + { + return ['text', 'json']; + } } diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index 54f4e233b884d..e059740a1375d 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -48,10 +48,13 @@ public function __construct( parent::__construct(); } + /** + * @return void + */ protected function configure() { $this - ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) ->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors') ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') ->setHelp(<<<'EOF' @@ -140,7 +143,7 @@ private function getFilesInfo(array $filenames): array return $filesInfo; } - protected function findFiles(string $filename) + protected function findFiles(string $filename): iterable { if (is_file($filename)) { return [$filename]; @@ -169,17 +172,17 @@ private function validate(string $template, string $file): array return ['template' => $template, 'file' => $file, 'valid' => true]; } - private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files) + private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files): int { return match ($this->format) { 'txt' => $this->displayTxt($output, $io, $files), 'json' => $this->displayJson($output, $files), 'github' => $this->displayTxt($output, $io, $files, true), - default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; } - private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false) + private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int { $errors = 0; $githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($output) : null; @@ -202,7 +205,7 @@ private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $fi return min($errors, 1); } - private function displayJson(OutputInterface $output, array $filesInfo) + private function displayJson(OutputInterface $output, array $filesInfo): int { $errors = 0; @@ -221,7 +224,7 @@ private function displayJson(OutputInterface $output, array $filesInfo) return min($errors, 1); } - private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null) + private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null): void { $line = $exception->getTemplateLine(); @@ -254,7 +257,7 @@ private function renderException(SymfonyStyle $output, string $template, Error $ } } - private function getContext(string $template, int $line, int $context = 3) + private function getContext(string $template, int $line, int $context = 3): array { $lines = explode("\n", $template); @@ -273,7 +276,12 @@ private function getContext(string $template, int $line, int $context = 3) public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues(['txt', 'json', 'github']); + $suggestions->suggestValues($this->getAvailableFormatOptions()); } } + + private function getAvailableFormatOptions(): array + { + return ['txt', 'json', 'github']; + } } diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php index a8da618bc0738..e261a0505b048 100644 --- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php +++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php @@ -38,18 +38,18 @@ public function __construct(Profile $profile, Environment $twig = null) $this->twig = $twig; } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { } - public function reset() + public function reset(): void { $this->profile->reset(); unset($this->computed); $this->data = []; } - public function lateCollect() + public function lateCollect(): void { $this->data['profile'] = serialize($this->profile); $this->data['template_paths'] = []; @@ -78,37 +78,37 @@ public function lateCollect() $templateFinder($this->profile); } - public function getTime() + public function getTime(): float { return $this->getProfile()->getDuration() * 1000; } - public function getTemplateCount() + public function getTemplateCount(): int { return $this->getComputedData('template_count'); } - public function getTemplatePaths() + public function getTemplatePaths(): array { return $this->data['template_paths']; } - public function getTemplates() + public function getTemplates(): array { return $this->getComputedData('t 97AE emplates'); } - public function getBlockCount() + public function getBlockCount(): int { return $this->getComputedData('block_count'); } - public function getMacroCount() + public function getMacroCount(): int { return $this->getComputedData('macro_count'); } - public function getHtmlCallGraph() + public function getHtmlCallGraph(): Markup { $dumper = new HtmlDumper(); $dump = $dumper->dump($this->getProfile()); @@ -129,7 +129,7 @@ public function getHtmlCallGraph() return new Markup($dump, 'UTF-8'); } - public function getProfile() + public function getProfile(): Profile { return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', Profile::class]]); } @@ -141,7 +141,7 @@ private function getComputedData(string $index) return $this->computed[$index]; } - private function computeData(Profile $profile) + private function computeData(Profile $profile): array { $data = [ 'template_count' => 0, diff --git a/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php b/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php index 96924442d1e48..ef0f9ba9544e0 100644 --- a/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php +++ b/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php @@ -28,6 +28,9 @@ public function __construct( ) { } + /** + * @return void + */ public function onKernelView(ViewEvent $event) { $parameters = $event->getControllerResult(); diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index dd2e0682d2901..748d60cb154b1 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -120,9 +120,7 @@ public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?stri // remove main code/span tags $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); // split multiline spans - $code = preg_replace_callback('#]++)>((?:[^<]*+
)++[^<]*+)
#', function ($m) { - return "".str_replace('
', "

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

", $m[2]).'', $code); $content = explode('
', $code); $lines = []; @@ -188,9 +186,7 @@ public function getFileRelative(string $file): ?string public function formatFileFromText(string $text): string { - return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) { - return 'in '.$this->formatFile($match[2], $match[3]); - }, $text); + return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text); } /** diff --git a/src/Symfony/Bridge/Twig/Extension/ImportMapExtension.php b/src/Symfony/Bridge/Twig/Extension/ImportMapExtension.php new file mode 100644 index 0000000000000..2156c74d5a0c9 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/ImportMapExtension.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * @author Kévin Dunglas + */ +final class ImportMapExtension extends AbstractExtension +{ + public function getFunctions(): array + { + return [ + new TwigFunction('importmap', [ImportMapRuntime::class, 'importmap'], ['is_safe' => ['html']]), + ]; + } +} diff --git a/src/Symfony/Bridge/Twig/Extension/ImportMapRuntime.php b/src/Symfony/Bridge/Twig/Extension/ImportMapRuntime.php new file mode 100644 index 0000000000000..aa5097d35de5d --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/ImportMapRuntime.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\Bridge\Twig\Extension; + +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; + +/** + * @author Kévin Dunglas + */ +class ImportMapRuntime +{ + public function __construct(private readonly ImportMapRenderer $importMapRenderer) + { + } + + public function importmap(?string $entryPoint = 'app'): string + { + return $this->importMapRenderer->render($entryPoint); + } +} diff --git a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php index 270583f27a995..5827640d5bd0d 100644 --- a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php @@ -79,8 +79,8 @@ public function isUrlGenerationSafe(Node $argsNode): array $argsNode->hasNode(1) ? $argsNode->getNode(1) : null ); - if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 && - (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression) + if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 + && (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression) ) { return ['html']; } diff --git a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php index 71f556aead03e..661a063f98e61 100644 --- a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @@ -99,7 +99,7 @@ public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, st * Use a string (the place name) to get place metadata * Use a Transition instance to get transition metadata */ - public function getMetadata(object $subject, string $key, string|Transition $metadataSubject = null, string $name = null) + public function getMetadata(object $subject, string $key, string|Transition $metadataSubject = null, string $name = null): mixed { return $this ->workflowRegistry diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php index 7f733455e824a..fbe8e0b3e43dc 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php @@ -132,6 +132,8 @@ protected function loadResourceForBlockName(string $cacheKey, FormView $view, st * to initialize the theme first. Any changes made to * this variable will be kept and be available upon * further calls to this method using the same theme. + * + * @return void */ protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme) { diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index be6fea5c9cc56..5bd54e64463e5 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -38,7 +38,7 @@ class NotificationEmail extends TemplatedEmail 'action_url' => null, 'markdown' => false, 'raw' => false, - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', ]; private bool $rendered = false; @@ -54,7 +54,7 @@ public function __construct(Headers $headers = null, AbstractPart $body = null) } if ($missingPackages) { - throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available; try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); + throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available. Try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); } parent::__construct($headers, $body); @@ -88,7 +88,7 @@ public function markAsPublic(): static public function markdown(string $content): static { if (!class_exists(MarkdownExtension::class)) { - throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available; try running "composer require twig/markdown-extra".', __METHOD__)); + throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available. Try running "composer require twig/markdown-extra".', __METHOD__)); } $this->context['markdown'] = true; diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php index 29cb13d0b2e5b..d5e95040d6bf2 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php @@ -56,9 +56,9 @@ protected function doEnterNode(Node $node, Environment $env): Node } if ( - $node instanceof FilterExpression && - 'trans' === $node->getNode('filter')->getAttribute('value') && - $node->getNode('node') instanceof ConstantExpression + $node instanceof FilterExpression + && 'trans' === $node->getNode('filter')->getAttribute('value') + && $node->getNode('node') instanceof ConstantExpression ) { // extract constant nodes with a trans filter $this->messages[] = [ @@ -66,8 +66,8 @@ protected function doEnterNode(Node $node, Environment $env): Node $this->getReadDomainFromArguments($node->getNode('arguments'), 1), ]; } elseif ( - $node instanceof FunctionExpression && - 't' === $node->getAttribute('name') + $node instanceof FunctionExpression + && 't' === $node->getAttribute('name') ) { $nodeArguments = $node->getNode('arguments'); @@ -84,10 +84,10 @@ protected function doEnterNode(Node $node, Environment $env): Node $node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null, ]; } elseif ( - $node instanceof FilterExpression && - 'trans' === $node->getNode('filter')->getAttribute('value') && - $node->getNode('node') instanceof ConcatBinary && - $message = $this->getConcatValueFromNode($node->getNode('node'), null) + $node instanceof FilterExpression + && 'trans' === $node->getNode('filter')->getAttribute('value') + && $node->getNode('node') instanceof ConcatBinary + && $message = $this->getConcatValueFromNode($node->getNode('node'), null) ) { $this->messages[] = [ $message, diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig index 252b95e4f3293..cb5a60e079861 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig @@ -353,7 +353,7 @@ {%- block form_errors -%} {%- if errors|length > 0 -%} {%- for error in errors -%} -
{{ error.message }}
+
{{ error.message }}
{%- endfor -%} {%- endif %} {%- endblock form_errors %} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 61f64e33fa751..48ef558385b94 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -61,7 +61,7 @@ {%- endif -%} {% if placeholder is not none -%} - + {%- endif %} {%- if preferred_choices|length > 0 -%} {% set options = preferred_choices %} diff --git a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php index 219d47d1013d5..764eade4d9171 100644 --- a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php +++ b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php @@ -20,6 +20,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Translation\LocaleSwitcher; class AppVariableTest extends TestCase { @@ -104,6 +105,16 @@ public function testGetUser() $this->assertEquals($user, $this->appVariable->getUser()); } + public function testGetLocale() + { + $localeSwitcher = $this->createMock(LocaleSwitcher::class); + $this->appVariable->setLocaleSwitcher($localeSwitcher); + + $localeSwitcher->method('getLocale')->willReturn('fr'); + + self::assertEquals('fr', $this->appVariable->getLocale()); + } + public function testGetTokenWithNoToken() { $tokenStorage = $this->createMock(TokenStorageInterface::class); @@ -156,6 +167,13 @@ public function testGetSessionWithRequestStackNotSet() $this->appVariable->getSession(); } + public function testGetLocaleWithLocaleSwitcherNotSet() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The "app.locale" variable is not available.'); + $this->appVariable->getLocale(); + } + public function testGetFlashesWithNoRequest() { $this->setRequestStack(null); diff --git a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php index bae8e727fd7a0..75203f9c899e0 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php @@ -159,9 +159,7 @@ private function createCommandTester(): CommandTester private function createCommand(): Command { $environment = new Environment(new FilesystemLoader(\dirname(__DIR__).'/Fixtures/templates/')); - $environment->addFilter(new TwigFilter('deprecated_filter', function ($v) { - return $v; - }, ['deprecated' => true])); + $environment->addFilter(new TwigFilter('deprecated_filter', fn ($v) => $v, ['deprecated' => true])); $command = new LintCommand($environment); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php index e79b0c3141e9e..3a4104bb6adbd 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php @@ -15,7 +15,7 @@ abstract class AbstractBootstrap3HorizontalLayoutTestCase extends AbstractBootst { public function testLabelOnForm() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 16cb900b99522..7f4a77e2d5bf7 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -13,13 +13,12 @@ use Symfony\Component\Form\Extension\Core\Type\PercentType; use Symfony\Component\Form\FormError; -use Symfony\Component\Form\Tests\AbstractLayoutTestCase; abstract class AbstractBootstrap3LayoutTestCase extends AbstractLayoutTestCase { public function testLabelOnForm() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); @@ -1039,9 +1038,7 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => false, 'expanded' => true, ]); @@ -1404,9 +1401,7 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => true, 'expanded' => true, ]); @@ -1565,6 +1560,7 @@ public function testDateTime() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', date('Y').'-02-03 04:05:06', [ 'input' => 'string', 'with_seconds' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1602,6 +1598,7 @@ public function testDateTimeWithPlaceholderGlobal() 'input' => 'string', 'placeholder' => 'Change&Me', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1641,6 +1638,7 @@ public function testDateTimeWithHourAndMinute() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', $data, [ 'input' => 'array', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1678,6 +1676,7 @@ public function testDateTimeWithSeconds() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', date('Y').'-02-03 04:05:06', [ 'input' => 'string', 'with_seconds' => true, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1757,7 +1756,7 @@ public function testDateTimeWithWidgetSingleText() [@type="datetime-local"] [@name="name"] [@class="my&class form-control"] - [@value="2011-02-03T04:05:06"] + [@value="2011-02-03T04:05"] ' ); } @@ -1911,6 +1910,7 @@ public function testBirthDay() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\BirthdayType', '2000-02-03', [ 'input' => 'string', + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1941,6 +1941,7 @@ public function testBirthDayWithPlaceholder() 'input' => 'string', 'placeholder' => '', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -2469,6 +2470,7 @@ public function testTime() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', '04:05:06', [ 'input' => 'string', 'with_seconds' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -2496,6 +2498,7 @@ public function testTimeWithSeconds() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', '04:05:06', [ 'input' => 'string', 'with_seconds' => true, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -2583,6 +2586,7 @@ public function testTimeWithPlaceholderGlobal() 'input' => 'string', 'placeholder' => 'Change&Me', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -2610,6 +2614,7 @@ public function testTimeWithPlaceholderOnYear() 'input' => 'string', 'required' => false, 'placeholder' => ['hour' => 'Change&Me'], + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php index ce8a733665854..723559ee3d985 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php @@ -47,7 +47,7 @@ public function testRow() public function testLabelOnForm() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTestCase.php index e329b5d49ec16..781d1c92cf92f 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTestCase.php @@ -57,7 +57,7 @@ public function testRow() public function testLabelOnForm() { - $form = $this->factory->createNamed('name', DateType::class); + $form = $this->factory->createNamed('name', DateType::class, null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); @@ -580,9 +580,7 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', ChoiceType::class, '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => false, 'expanded' => true, ]); @@ -901,9 +899,7 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', ChoiceType::class, ['&a'], [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => true, 'expanded' => true, ]); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php index 770c18a1ba3e1..1c02f9e10207c 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5HorizontalLayoutTestCase.php @@ -28,9 +28,9 @@ abstract class AbstractBootstrap5HorizontalLayoutTestCase extends AbstractBootst { public function testRow() { - $form = $this->factory->createNamed('name', TextType::class); - $form->addError(new FormError('[trans]Error![/trans]')); - $html = $this->renderRow($form->createView()); + $form = $this->factory->createNamed('')->add('name', TextType::class); + $form->get('name')->addError(new FormError('[trans]Error![/trans]')); + $html = $this->renderRow($form->get('name')->createView()); $this->assertMatchesXpath($html, '/div @@ -55,9 +55,9 @@ public function testRow() public function testRowWithCustomClass() { - $form = $this->factory->createNamed('name', TextType::class); - $form->addError(new FormError('[trans]Error![/trans]')); - $html = $this->renderRow($form->createView(), [ + $form = $this->factory->createNamed('')->add('name', TextType::class); + $form->get('name')->addError(new FormError('[trans]Error![/trans]')); + $html = $this->renderRow($form->get('name')->createView(), [ 'row_attr' => [ 'class' => 'mb-5', ], @@ -86,7 +86,7 @@ public function testRowWithCustomClass() public function testLabelOnForm() { - $form = $this->factory->createNamed('name', DateType::class); + $form = $this->factory->createNamed('name', DateType::class, null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTestCase.php index a59e659728d50..630663a60da2a 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTestCase.php @@ -40,9 +40,9 @@ abstract class AbstractBootstrap5LayoutTestCase extends AbstractBootstrap4Layout { public function testRow() { - $form = $this->factory->createNamed('name', TextType::class); - $form->addError(new FormError('[trans]Error![/trans]')); - $html = $this->renderRow($form->createView()); + $form = $this->factory->createNamed('')->add('name', TextType::class); + $form->get('name')->addError(new FormError('[trans]Error![/trans]')); + $html = $this->renderRow($form->get('name')->createView()); $this->assertMatchesXpath($html, '/div @@ -61,9 +61,9 @@ public function testRow() public function testRowWithCustomClass() { - $form = $this->factory->createNamed('name', TextType::class); - $form->addError(new FormError('[trans]Error![/trans]')); - $html = $this->renderRow($form->createView(), [ + $form = $this->factory->createNamed('')->add('name', TextType::class); + $form->get('name')->addError(new FormError('[trans]Error![/trans]')); + $html = $this->renderRow($form->get('name')->createView(), [ 'row_attr' => [ 'class' => 'mb-5', ], @@ -309,11 +309,34 @@ public function testHelpHtmlIsTrue() public function testErrors() { - $form = $this->factory->createNamed('name', TextType::class); + self::markTestSkipped('This method has been split into testRootErrors() and testRowErrors().'); + } + + public function testRootErrors() + { + $form = $this->factory->createNamed(''); $form->addError(new FormError('[trans]Error 1[/trans]')); $form->addError(new FormError('[trans]Error 2[/trans]')); $html = $this->renderErrors($form->createView()); + $this->assertMatchesXpath($html, + '/div + [@class="alert alert-danger d-block"] + [.="[trans]Error 1[/trans]"] + /following-sibling::div + [@class="alert alert-danger d-block"] + [.="[trans]Error 2[/trans]"] +' + ); + } + + public function testRowErrors() + { + $form = $this->factory->createNamed('')->add('name', TextType::class); + $form->get('name')->addError(new FormError('[trans]Error 1[/trans]')); + $form->get('name')->addError(new FormError('[trans]Error 2[/trans]')); + $html = $this->renderErrors($form->get('name')->createView()); + $this->assertMatchesXpath($html, '/div [@class="invalid-feedback d-block"] @@ -983,6 +1006,7 @@ public function testDateTime() $form = $this->factory->createNamed('name', DateTimeType::class, date('Y').'-02-03 04:05:06', [ 'input' => 'string', 'with_seconds' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1036,6 +1060,7 @@ public function testDateTimeWithPlaceholderGlobal() 'input' => 'string', 'placeholder' => 'Change&Me', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1089,6 +1114,7 @@ public function testDateTimeWithHourAndMinute() $form = $this->factory->createNamed('name', DateTimeType::class, $data, [ 'input' => 'array', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1140,6 +1166,7 @@ public function testDateTimeWithSeconds() $form = $this->factory->createNamed('name', DateTimeType::class, date('Y').'-02-03 04:05:06', [ 'input' => 'string', 'with_seconds' => true, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1364,6 +1391,7 @@ public function testBirthDay() { $form = $this->factory->createNamed('name', BirthdayType::class, '2000-02-03', [ 'input' => 'string', + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1398,6 +1426,7 @@ public function testBirthDayWithPlaceholder() 'input' => 'string', 'placeholder' => '', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1575,6 +1604,7 @@ public function testTime() $form = $this->factory->createNamed('name', TimeType::class, '04:05:06', [ 'input' => 'string', 'with_seconds' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1608,6 +1638,7 @@ public function testTimeWithSeconds() $form = $this->factory->createNamed('name', TimeType::class, '04:05:06', [ 'input' => 'string', 'with_seconds' => true, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1691,6 +1722,7 @@ public function testTimeWithPlaceholderGlobal() 'input' => 'string', 'placeholder' => 'Change&Me', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], @@ -1724,6 +1756,7 @@ public function testTimeWithPlaceholderOnYear() 'input' => 'string', 'required' => false, 'placeholder' => ['hour' => 'Change&Me'], + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php similarity index 99% rename from src/Symfony/Component/Form/Tests/AbstractDivLayoutTestCase.php rename to src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php index db6054ad85fce..a02fca4bc54ca 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Form\Tests; +namespace Symfony\Bridge\Twig\Tests\Extension; use Symfony\Component\Form\FormError; use Symfony\Component\Security\Csrf\CsrfToken; @@ -763,9 +763,7 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => false, 'expanded' => true, ]); @@ -840,9 +838,7 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', ['&a'], [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], - 'choice_label' => function () { - return false; - }, + 'choice_label' => fn () => false, 'multiple' => true, 'expanded' => true, ]); diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php similarity index 99% rename from src/Symfony/Component/Form/Tests/AbstractLayoutTestCase.php rename to src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php index 744212d1f4086..af1a013a556fa 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Form\Tests; +namespace Symfony\Bridge\Twig\Tests\Extension; use PHPUnit\Framework\SkippedTestError; use Symfony\Component\Form\Extension\Core\Type\PercentType; @@ -18,6 +18,7 @@ use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormView; use Symfony\Component\Form\Test\FormIntegrationTestCase; +use Symfony\Component\Form\Tests\VersionAwareTest; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatableMessage; use Symfony\ 741A Contracts\Translation\TranslatableInterface; @@ -175,7 +176,7 @@ public function testLabelWithoutTranslation() public function testLabelOnForm() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType', null, ['widget' => 'choice']); $view = $form->createView(); $this->renderWidget($view, ['label' => 'foo']); $html = $this->renderLabel($view); @@ -1327,6 +1328,7 @@ public function testDateTime() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', date('Y').'-02-03 04:05:06', [ 'input' => 'string', 'with_seconds' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), [], @@ -1367,6 +1369,7 @@ public function testDateTimeWithPlaceholderGlobal() 'input' => 'string', 'placeholder' => 'Change&Me', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), [], @@ -1408,6 +1411,7 @@ public function testDateTimeWithHourAndMinute() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', $data, [ 'input' => 'array', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), [], @@ -1447,6 +1451,7 @@ public function testDateTimeWithSeconds() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', date('Y').'-02-03 04:05:06', [ 'input' => 'string', 'with_seconds' => true, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), [], @@ -1523,7 +1528,7 @@ public function testDateTimeWithWidgetSingleText() '/input [@type="datetime-local"] [@name="name"] - [@value="2011-02-03T04:05:06"] + [@value="2011-02-03T04:05"] ' ); } @@ -1654,7 +1659,7 @@ public function testDateSingleText() public function testDateErrorBubbling() { $form = $this->factory->createNamedBuilder('form', 'Symfony\Component\Form\Extension\Core\Type\FormType') - ->add('date', 'Symfony\Component\Form\Extension\Core\Type\DateType') + ->add('date', 'Symfony\Component\Form\Extension\Core\Type\DateType', ['widget' => 'choice']) ->getForm(); $form->get('date')->addError(new FormError('[trans]Error![/trans]')); $view = $form->createView(); @@ -1667,6 +1672,7 @@ public function testBirthDay() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\BirthdayType', '2000-02-03', [ 'input' => 'string', + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), [], @@ -1693,6 +1699,7 @@ public function testBirthDayWithPlaceholder() 'input' => 'string', 'placeholder' => '', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), [], @@ -2127,6 +2134,7 @@ public function testTime() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', '04:05:06', [ 'input' => 'string', 'with_seconds' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), [], @@ -2151,6 +2159,7 @@ public function testTimeWithSeconds() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TimeType', '04:05:06', [ 'input' => 'string', 'with_seconds' => true, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), [], @@ -2230,6 +2239,7 @@ public function testTimeWithPlaceholderGlobal() 'input' => 'string', 'placeholder' => 'Change&Me', 'required' => false, + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), [], @@ -2255,6 +2265,7 @@ public function testTimeWithPlaceholderOnYear() 'input' => 'string', 'required' => false, 'placeholder' => ['hour' => 'Change&Me'], + 'widget' => 'choice', ]); $this->assertWidgetMatchesXpath($form->createView(), [], @@ -2277,7 +2288,7 @@ public function testTimeWithPlaceholderOnYear() public function testTimeErrorBubbling() { $form = $this->factory->createNamedBuilder('form', 'Symfony\Component\Form\Extension\Core\Type\FormType') - ->add('time', 'Symfony\Component\Form\Extension\Core\Type\TimeType') + ->add('time', 'Symfony\Component\Form\Extension\Core\Type\TimeType', ['widget' => 'choice']) ->getForm(); $form->get('time')->addError(new FormError('[trans]Error![/trans]')); $view = $form->createView(); diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php similarity index 99% rename from src/Symfony/Component/Form/Tests/AbstractTableLayoutTestCase.php rename to src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php index 09d669129fce9..c3cdb08e547e8 100644 --- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Form\Tests; +namespace Symfony\Bridge\Twig\Tests\Extension; use Symfony\Component\Form\FormError; use Symfony\Component\Security\Csrf\CsrfToken; diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index 9fa6a01316ba0..19efa9455d3d1 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -18,7 +18,6 @@ use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Tests\AbstractDivLayoutTestCase; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; use Twig\Loader\FilesystemLoader; diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php index c0a7a9e8a8002..c94f9b5c0d1f1 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php @@ -17,7 +17,6 @@ use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Tests\AbstractTableLayoutTestCase; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; use Twig\Loader\FilesystemLoader; diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php new file mode 100644 index 0000000000000..26a572e1954f5 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Extension; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Extension\ImportMapExtension; +use Symfony\Bridge\Twig\Extension\ImportMapRuntime; +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; +use Twig\Environment; +use Twig\Loader\ArrayLoader; +use Twig\RuntimeLoader\RuntimeLoaderInterface; + +class ImportMapExtensionTest extends TestCase +{ + public function testItRendersTheImportmap() + { + $twig = new Environment(new ArrayLoader([ + 'template' => '{{ importmap("application") }}', + ]), ['debug' => true, 'cache' => false, 'autoescape' => 'html', 'optimizations' => 0]); + $twig->addExtension(new ImportMapExtension()); + $importMapRenderer = $this->createMock(ImportMapRenderer::class); + $expected = ''; + $importMapRenderer->expects($this->once()) + ->method('render') + ->with('application') + ->willReturn($expected); + $runtime = new ImportMapRuntime($importMapRenderer); + + $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); + $mockRuntimeLoader + ->method('load') + ->willReturnMap([ + [ImportMapRuntime::class, $runtime], + ]) + ; + $twig->addRuntimeLoader($mockRuntimeLoader); + + $this->assertSame($expected, $twig->render('template')); + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php index 231067e0365dc..6af152dad6c5e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php @@ -124,9 +124,7 @@ public function testRenderedOnceUnserializableContext() ; $email->textTemplate('text'); $email->context([ - 'foo' => static function () { - return 'bar'; - }, + 'foo' => static fn () => 'bar', ]); $renderer->render($email); diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php index ceafea1bb6b72..6e48f2b4a5d61 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php @@ -35,7 +35,7 @@ public function test() 'markdown' => true, 'raw' => false, 'a' => 'b', - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', ], $email->getContext()); } @@ -58,7 +58,7 @@ public function testSerialize() 'markdown' => false, 'raw' => true, 'a' => 'b', - 'footer_text' => 'Notification e-mail sent by Symfony', + 'footer_text' => 'Notification email sent by Symfony', ], $email->getContext()); $this->assertSame('@email/example/notification/body.html.twig', $email->getHtmlTemplate()); diff --git a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php index 0707359dd16cf..2b44c5ef8d90a 100644 --- a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php +++ b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php @@ -45,6 +45,9 @@ public function __construct(Environment $twig) $this->twig = $twig; } + /** + * @return void + */ public function extract($resource, MessageCatalogue $catalogue) { foreach ($this->extractFiles($resource) as $file) { @@ -56,11 +59,17 @@ public function extract($resource, MessageCatalogue $catalogue) } } + /** + * @return void + */ public function setPrefix(string $prefix) { $this->prefix = $prefix; } + /** + * @return void + */ protected function extractTemplate(string $template, MessageCatalogue $catalogue) { $visitor = $this->twig->getExtension(TranslationExtension::class)->getTranslationNodeVisitor(); diff --git a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php index c4d3971edcaff..9368f15b96d74 100644 --- a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php +++ b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php @@ -87,7 +87,7 @@ public static function onUndefinedFunction(string $name): TwigFunction|false } if ('webpack-encore-bundle' === self::FUNCTION_COMPONENTS[$name]) { - return new TwigFunction($name, static function () { return ''; }); + return new TwigFunction($name, static fn () => ''); } throw new SyntaxError(self::onUndefined($name, 'function', self::FUNCTION_COMPONENTS[$name])); diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index a091f85808dd5..cac49224c88be 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^2.13|^3.0.4" }, "require-dev": { @@ -26,9 +26,10 @@ "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", - "symfony/form": "^6.2.7", + "symfony/form": "^6.3", "symfony/html-sanitizer": "^6.1", "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^6.2", @@ -37,7 +38,7 @@ "symfony/polyfill-intl-icu": "~1.0", "symfony/property-info": "^5.4|^6.0", "symfony/routing": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", + "symfony/translation": "^6.1", "symfony/yaml": "^5.4|^6.0", "symfony/security-acl": "^2.8|^3.0", "symfony/security-core": "^5.4|^6.0", @@ -57,30 +58,13 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/console": "<5.4", - "symfony/form": "<6.2.7", + "symfony/form": "<6.3", "symfony/http-foundation": "<5.4", "symfony/http-kernel": "<6.2", "symfony/mime": "<6.2", "symfony/translation": "<5.4", "symfony/workflow": "<5.4" }, - "suggest": { - "symfony/finder": "", - "symfony/asset": "For using the AssetExtension", - "symfony/form": "For using the FormExtension", - "symfony/html-sanitizer": "For using the HtmlSanitizerExtension", - "symfony/http-kernel": "For using the HttpKernelExtension", - "symfony/routing": "For using the RoutingExtension", - "symfony/translation": "For using the TranslationExtension", - "symfony/yaml": "For using the YamlExtension", - "symfony/security-core": "For using the SecurityExtension", - "symfony/security-csrf": "For using the CsrfExtension", - "symfony/security-http": "For using the LogoutUrlExtension", - "symfony/stopwatch": "For using the StopwatchExtension", - "symfony/var-dumper": "For using the DumpExtension", - "symfony/expression-language": "For using the ExpressionExtension", - "symfony/web-link": "For using the WebLinkExtension" - }, "autoload": { "psr-4": { "Symfony\\Bridge\\Twig\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php b/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php index cc8588f1ceb6c..f0cffcd238ece 100644 --- a/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php +++ b/src/Symfony/Bundle/DebugBundle/Command/ServerDumpPlaceholderCommand.php @@ -38,7 +38,7 @@ public function __construct(DumpServer $server = null, array $descriptors = []) parent::__construct(); } - protected function configure() + protected function configure(): void { $this->setDefinition($this->replacedCommand->getDefinition()); $this->setHelp($this->replacedCommand->getHelp()); diff --git a/src/Symfony/Bundle/DebugBundle/DebugBundle.php b/src/Symfony/Bundle/DebugBundle/DebugBundle.php index 045ea5f8dbd65..a24321e22910d 100644 --- a/src/Symfony/Bundle/DebugBundle/DebugBundle.php +++ b/src/Symfony/Bundle/DebugBundle/DebugBundle.php @@ -22,6 +22,9 @@ */ class DebugBundle extends Bundle { + /** + * @return void + */ public function boot() { if ($this->container->getParameter('kernel.debug')) { @@ -44,6 +47,9 @@ public function boot() } } + /** + * @return void + */ public function build(ContainerBuilder $container) { parent::build($container); @@ -51,6 +57,9 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new DumpDataCollectorPass()); } + /** + * @return void + */ public function registerCommands(Application $application) { // noop diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php index 5c531cc91dbcd..cecce87c4a4e7 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php @@ -22,6 +22,9 @@ */ class DumpDataCollectorPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('data_collector.dump')) { diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php index 2d077ba681f5e..eadeafba55832 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php @@ -29,6 +29,9 @@ */ class DebugExtension extends Extension { + /** + * @return void + */ public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); diff --git a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/Compiler/DumpDataCollectorPassTest.php b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/Compiler/DumpDataCollectorPassTest.php index 0b518ee00f71a..769a1421d9c7f 100644 --- a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/Compiler/DumpDataCollectorPassTest.php +++ b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/Compiler/DumpDataCollectorPassTest.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; class DumpDataCollectorPassTest extends TestCase { @@ -25,7 +26,7 @@ public function testProcessWithoutFileLinkFormatParameter() $container = new ContainerBuilder(); $container->addCompilerPass(new DumpDataCollectorPass()); - $definition = new Definition('Symfony\Component\HttpKernel\DataCollector\DumpDataCollector', [null, null, null, null]); + $definition = new Definition(DumpDataCollector::class, [null, null, null, null]); $container->setDefinition('data_collector.dump', $definition); $container->compile(); @@ -39,7 +40,7 @@ public function testProcessWithToolbarEnabled() $container->addCompilerPass(new DumpDataCollectorPass()); $requestStack = new RequestStack(); - $definition = new Definition('Symfony\Component\HttpKernel\DataCollector\DumpDataCollector', [null, null, null, $requestStack]); + $definition = new Definition(DumpDataCollector::class, [null, null, null, $requestStack]); $container->setDefinition('data_collector.dump', $definition); $container->setParameter('web_profiler.debug_toolbar.mode', WebDebugToolbarListener::ENABLED); @@ -53,7 +54,7 @@ public function testProcessWithToolbarDisabled() $container = new ContainerBuilder(); $container->addCompilerPass(new DumpDataCollectorPass()); - $definition = new Definition('Symfony\Component\HttpKernel\DataCollector\DumpDataCollector', [null, null, null, new RequestStack()]); + $definition = new Definition(DumpDataCollector::class, [null, null, null, new RequestStack()]); $container->setDefinition('data_collector.dump', $definition); $container->setParameter('web_profiler.debug_toolbar.mode', WebDebugToolbarListener::DISABLED); @@ -67,7 +68,7 @@ public function testProcessWithoutToolbar() $container = new ContainerBuilder(); $container->addCompilerPass(new DumpDataCollectorPass()); - $definition = new Definition('Symfony\Component\HttpKernel\DataCollector\DumpDataCollector', [null, null, null, new RequestStack()]); + $definition = new Definition(DumpDataCollector::class, [null, null, null, new RequestStack()]); $container->setDefinition('data_collector.dump', $definition); $container->compile(); diff --git a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php index 66c641de6460e..51090815b9cc1 100644 --- a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php +++ b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\DebugBundle\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; +use Symfony\Bundle\DebugBundle\DebugBundle; use Symfony\Bundle\DebugBundle\DependencyInjection\DebugExtension; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -114,7 +115,7 @@ private function createContainer() 'kernel.charset' => 'UTF-8', 'kernel.debug' => true, 'kernel.project_dir' => __DIR__, - 'kernel.bundles' => ['DebugBundle' => 'Symfony\\Bundle\\DebugBundle\\DebugBundle'], + 'kernel.bundles' => ['DebugBundle' => DebugBundle::class], ])); return $container; diff --git a/src/Symfony/Bundle/DebugBundle/composer.json b/src/Symfony/Bundle/DebugBundle/composer.json index 619db7d66feed..3b7607c7d39f7 100644 --- a/src/Symfony/Bundle/DebugBundle/composer.json +++ b/src/Symfony/Bundle/DebugBundle/composer.json @@ -31,10 +31,6 @@ "symfony/config": "<5.4", "symfony/dependency-injection": "<5.4" }, - "suggest": { - "symfony/config": "For service container configuration", - "symfony/dependency-injection": "For using as a service from the container" - }, "autoload": { "psr-4": { "Symfony\\Bundle\\DebugBundle\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 1d43a095ed1c5..0e59213f54c29 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,28 @@ CHANGELOG ========= +6.3 +--- + + * Add `extra` option for `http_client.default_options` and `http_client.scoped_client` + * Add `DomCrawlerAssertionsTrait::assertSelectorCount(int $count, string $selector)` + * Allow to avoid `limit` definition in a RateLimiter configuration when using the `no_limit` policy + * Add `--format` option to the `debug:config` command + * Add support to pass namespace wildcard in `framework.messenger.routing` + * Deprecate `framework:exceptions` tag, unwrap it and replace `framework:exception` tags' `name` attribute by `class` + * Deprecate the `notifier.logger_notification_listener` service, use the `notifier.notification_logger_listener` service instead + * Allow setting private services with the test container + * Register alias for argument for workflow services with workflow name only + * Configure the `ErrorHandler` on `FrameworkBundle::boot()` + * Allow setting `debug.container.dump` to `false` to disable dumping the container to XML + * Add `framework.http_cache.skip_response_headers` option + * Display warmers duration on debug verbosity for `cache:clear` command + * Add `AbstractController::sendEarlyHints()` to send HTTP Early Hints + * Add autowiring aliases for `Http\Client\HttpAsyncClient` + * Deprecate the `Http\Client\HttpClient` service, use `Psr\Http\Client\ClientInterface` instead + * Add `stop_worker_on_signals` configuration option to `messenger` to define signals which would stop a worker + * Add support for `--all` option to clear all cache pools with `cache:pool:clear` command + 6.2 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php index 47b3db6d2c9d3..9409bba2e04b4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php @@ -53,7 +53,7 @@ public function warmUp(string $cacheDir): array // the ArrayAdapter stores the values serialized // to avoid mutation of the data after it was written to the cache // so here we un-serialize the values first - $values = array_map(function ($val) { return null !== $val ? unserialize($val) : null; }, $arrayAdapter->getValues()); + $values = array_map(fn ($val) => null !== $val ? unserialize($val) : null, $arrayAdapter->getValues()); return $this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()), $values); } diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php index 5ecc25b3d7078..e489a0bbbec2b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php @@ -71,12 +71,12 @@ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter): bool protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values): array { // make sure we don't cache null values - $values = array_filter($values, function ($val) { return null !== $val; }); + $values = array_filter($values, fn ($val) => null !== $val); return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); } - private function readAllComponents(Reader $reader, string $class) + private function readAllComponents(Reader $reader, string $class): void { $reflectionClass = new \ReflectionClass($class); diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php index ba0dede3948a2..6325267e2ba4c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php @@ -71,7 +71,7 @@ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter): bool protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values): array { // make sure we don't cache null values - $values = array_filter($values, function ($val) { return null !== $val; }); + $values = array_filter($values, fn ($val) => null !== $val); return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php index f39950587c3e8..4f68228ee5200 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php @@ -31,7 +31,7 @@ #[AsCommand(name: 'about', description: 'Display information about the current project')] class AboutCommand extends Command { - protected function configure() + protected function configure(): void { $this ->setHelp(<<<'EOT' diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php index 5f79d95bf036d..5774a7c6f5906 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php @@ -35,9 +35,7 @@ protected function listBundles(OutputInterface|StyleInterface $output) $rows = []; $bundles = $this->getApplication()->getKernel()->getBundles(); - usort($bundles, function ($bundleA, $bundleB) { - return strcmp($bundleA->getName(), $bundleB->getName()); - }); + usort($bundles, fn ($bundleA, $bundleB) => strcmp($bundleA->getName(), $bundleB->getName())); foreach ($bundles as $bundle) { $extension = $bundle->getContainer F438 Extension(); @@ -131,7 +129,7 @@ public function validateConfiguration(ExtensionInterface $extension, mixed $conf } } - private function initializeBundles() + private function initializeBundles(): array { // Re-build bundle manually to initialize DI extensions that can be extended by other bundles in their build() method // as this method is not called when the container is loaded from the cache. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index 2ee2d93988864..264955d7951eb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -52,7 +52,7 @@ public function __construct(Filesystem $filesystem, string $projectDir) $this->projectDir = $projectDir; } - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -226,7 +226,7 @@ private function absoluteSymlinkWithFallback(string $originDir, string $targetDi * * @throws IOException if link cannot be created */ - private function symlink(string $originDir, string $targetDir, bool $relative = false) + private function symlink(string $originDir, string $targetDir, bool $relative = false): void { if ($relative) { $this->filesystem->mkdir(\dirname($targetDir)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php index 5ac0c8cbc2cb1..0b1038e1cb424 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php @@ -39,7 +39,7 @@ protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilde return $this->containerBuilder; } - if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) { + if (!$kernel->isDebug() || !$kernel->getContainer()->getParameter('debug.container.dump') || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) { $buildContainer = \Closure::bind(function () { $this->initializeBundles(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index ca46a577154a3..612105dfba59c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -48,7 +48,7 @@ public function __construct(CacheClearerInterface $cacheClearer, Filesystem $fil $this->filesystem = $filesystem ?? new Filesystem(); } - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -132,7 +132,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $warmer = $kernel->getContainer()->get('cache_warmer'); // non optional warmers already ran during container compilation $warmer->enableOnlyOptionalWarmers(); - $preload = (array) $warmer->warmUp($realCacheDir); + $preload = (array) $warmer->warmUp($realCacheDir, $io); if ($preload && file_exists($preloadFile = $realCacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { Preloader::append($preloadFile, $preload); @@ -145,7 +145,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($output->isVerbose()) { $io->comment('Warming up cache...'); } - $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers')); + $this->warmup($io, $warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers')); } if (!$fs->exists($warmupDir.'/'.$containerDir)) { @@ -219,7 +219,7 @@ private function isNfs(string $dir): bool return false; } - private function warmup(string $warmupDir, string $realBuildDir, bool $enableOptionalWarmers = true) + private function warmup(SymfonyStyle $io, string $warmupDir, string $realBuildDir, bool $enableOptionalWarmers = true): void { // create a temporary kernel $kernel = $this->getApplication()->getKernel(); @@ -233,7 +233,7 @@ private function warmup(string $warmupDir, string $realBuildDir, bool $enableOpt $warmer = $kernel->getContainer()->get('cache_warmer'); // non optional warmers already ran during container compilation $warmer->enableOnlyOptionalWarmers(); - $preload = (array) $warmer->warmUp($warmupDir); + $preload = (array) $warmer->warmUp($warmupDir, $io); if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { Preloader::append($preloadFile, $preload); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php index 99ebf9a979595..5569a5ab19ffe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php @@ -19,6 +19,7 @@ use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; @@ -45,12 +46,13 @@ public function __construct(Psr6CacheClearer $poolClearer, array $poolNames = nu $this->poolNames = $poolNames; } - protected function configure() + protected function configure(): void { $this ->setDefinition([ - new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'A list of cache pools or cache pool clearers'), + new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'A list of cache pools or cache pool clearers'), ]) + ->addOption('all', null, InputOption::VALUE_NONE, 'Clear all cache pools') ->setHelp(<<<'EOF' The %command.name% command clears the given cache pools or cache pool clearers. @@ -67,7 +69,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int $pools = []; $clearers = []; - foreach ($input->getArgument('pools') as $id) { + $poolNames = $input->getArgument('pools'); + if ($input->getOption('all')) { + if (!$this->poolNames) { + throw new InvalidArgumentException('Could not clear all cache pools, try specifying a specific pool or cache clearer.'); + } + + $io->comment('Clearing all cache pools...'); + $poolNames = $this->poolNames; + } elseif (!$poolNames) { + throw new InvalidArgumentException('Either specify at least one pool name, or provide the --all option to clear all pools.'); + } + + foreach ($poolNames as $id) { if ($this->poolClearer->hasPool($id)) { $pools[$id] = $id; } else { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php index 2c0dddd0fd9f9..7b53ceb8ba94d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php @@ -43,7 +43,7 @@ public function __construct(Psr6CacheClearer $poolClearer, array $poolNames = nu $this->poolNames = $poolNames; } - protected function configure() + protected function configure(): void { $this ->setDefinition([ diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php index f1e05b0db0768..2659ad8fe05c2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php @@ -37,7 +37,7 @@ public function __construct(array $poolNames) $this->poolNames = $poolNames; } - protected function configure() + protected function configure(): void { $this ->setHelp(<<<'EOF' @@ -51,9 +51,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $io->table(['Pool name'], array_map(function ($pool) { - return [$pool]; - }, $this->poolNames)); + $io->table(['Pool name'], array_map(fn ($pool) => [$pool], $this->poolNames)); return 0; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php index 53f2b01dff994..fc0dc6d795e0d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php @@ -38,7 +38,7 @@ public function __construct(iterable $pools) $this->pools = $pools; } - protected function configure() + protected function configure(): void { $this ->setHelp(<<<'EOF' diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php index 48f7aa391bae4..d094902c36c55 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php @@ -39,7 +39,7 @@ public function __construct(CacheWarmerAggregate $cacheWarmer) $this->cacheWarmer = $cacheWarmer; } - protected function configure() + protected function configure(): void { $this ->setDefinition([ diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index c5078d1b5b401..f3eab9d6085cc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -38,15 +39,19 @@ #[AsCommand(name: 'debug:config', description: 'Dump the current configuration for an extension')] class ConfigDebugCommand extends AbstractConfigCommand { - protected function configure() + protected function configure(): void { + $commentedHelpFormats = array_map(static fn (string $format): string => sprintf('%s', $format), $this->getAvailableFormatOptions()); + $helpFormats = implode('", "', $commentedHelpFormats); + $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), new InputOption('resolve-env', null, InputOption::VALUE_NONE, 'Display resolved environment variable values instead of placeholders'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), class_exists(Yaml::class) ? 'yaml' : 'json'), ]) - ->setHelp(<<<'EOF' + ->setHelp(<<%command.name% command dumps the current configuration for an extension/bundle. @@ -55,6 +60,11 @@ protected function configure() php %command.full_name% framework php %command.full_name% FrameworkBundle +The --format option specifies the format of the configuration, +these are "{$helpFormats}". + + php %command.full_name% framework --format=json + For dumping a specific option, add its path as second argument: php %command.full_name% framework serializer.enabled @@ -92,12 +102,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int $config = $this->getConfig($extension, $container, $input->getOption('resolve-env')); + $format = $input->getOption('format'); + + if ('yaml' === $format && !class_exists(Yaml::class)) { + $errorIo->error('Setting the "format" option to "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=json" instead.'); + + return 1; + } + if (null === $path = $input->getArgument('path')) { $io->title( sprintf('Current configuration for %s', $name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name)) ); - $io->writeln(Yaml::dump([$extensionAlias => $config], 10)); + $io->writeln($this->convertToFormat([$extensionAlias => $config], $format)); return 0; } @@ -112,11 +130,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->title(sprintf('Current configuration for "%s.%s"', $extensionAlias, $path)); - $io->writeln(Yaml::dump($config, 10)); + $io->writeln($this->convertToFormat($config, $format)); return 0; } + private function convertToFormat(mixed $config, string $format): string + { + return match ($format) { + 'yaml' => Yaml::dump($config, 10), + 'json' => json_encode($config, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + }; + } + private function compileContainer(): ContainerBuilder { $kernel = clone $this->getApplication()->getKernel(); @@ -194,6 +221,10 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } catch (LogicException) { } } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } } private function getAvailableBundles(bool $alias): array @@ -206,7 +237,7 @@ private function getAvailableBundles(bool $alias): array return $availableBundles; } - private function getConfig(ExtensionInterface $extension, ContainerBuilder $container, bool $resolveEnvs = false) + private function getConfig(ExtensionInterface $extension, ContainerBuilder $container, bool $resolveEnvs = false): mixed { return $container->resolveEnvPlaceholders( $container->getParameterBag()->resolveValue( @@ -228,4 +259,9 @@ private static function buildPathsCompletion(array $paths, string $prefix = ''): return $completionPaths; } + + private function getAvailableFormatOptions(): array + { + return ['yaml', 'json']; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index c28c5a3d0de7a..11a0668561a22 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -39,15 +39,18 @@ #[AsCommand(name: 'config:dump-reference', description: 'Dump the default configuration for an extension')] class ConfigDumpReferenceCommand extends AbstractConfigCommand { - protected function configure() + protected function configure(): void { + $commentedHelpFormats = array_map(static fn (string $format): string => sprintf('%s', $format), $this->getAvailableFormatOptions()); + $helpFormats = implode('", "', $commentedHelpFormats); + $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (yaml or xml)', 'yaml'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'yaml'), ]) - ->setHelp(<<<'EOF' + ->setHelp(<<%command.name% command dumps the default configuration for an extension/bundle. @@ -56,9 +59,8 @@ protected function configure() php %command.full_name% framework php %command.full_name% FrameworkBundle -With the --format option specifies the format of the configuration, -this is either yaml or xml. -When the option is not provided, yaml is used. +The --format option specifies the format of the configuration, +these are "{$helpFormats}". php %command.full_name% FrameworkBundle --format=xml @@ -145,7 +147,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int break; default: $io->writeln($message); - throw new InvalidArgumentException('Only the yaml and xml formats are supported.'); + throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))); } $io->writeln(null === $path ? $dumper->dump($configuration, $extension->getNamespace()) : $dumper->dumpAtPath($configuration, $path)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index 293fccc20b18b..300fae1b8aa79 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -38,7 +38,7 @@ class ContainerDebugCommand extends Command { use BuildDebugContainerTrait; - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -52,7 +52,7 @@ protected function configure() new InputOption('types', null, InputOption::VALUE_NONE, 'Display types (classes/interfaces) available in the container'), new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Display a specific environment variable used in the container'), new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Display environment variables used in the container'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Display deprecations generated when compiling and warming up the container'), ]) @@ -203,8 +203,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestOptionValuesFor('format')) { - $helper = new DescriptorHelper(); - $suggestions->suggestValues($helper->getFormats()); + $suggestions->suggestValues($this->getAvailableFormatOptions()); return; } @@ -243,7 +242,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti * * @throws \InvalidArgumentException */ - protected function validateInput(InputInterface $input) + protected function validateInput(InputInterface $input): void { $options = ['tags', 'tag', 'parameters', 'parameter']; @@ -352,4 +351,9 @@ public function filterToServiceTypes(string $serviceId): bool return class_exists($serviceId) || interface_exists($serviceId, false); } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php index 8100793faf3dc..af08235512e33 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php @@ -33,7 +33,7 @@ final class ContainerLintCommand extends Command { private ContainerBuilder $containerBuilder; - protected function configure() + protected function configure(): void { $this ->setHelp('This command parses service definitions and ensures that injected values match the type declarations of each services\' class.') @@ -77,7 +77,7 @@ private function getContainerBuilder(): ContainerBuilder $kernel = $this->getApplication()->getKernel(); $kernelContainer = $kernel->getContainer(); - if (!$kernel->isDebug() || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) { + if (!$kernel->isDebug() || !$kernelContainer->getParameter('debug.container.dump') || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) { if (!$kernel instanceof Kernel) { throw new RuntimeException(sprintf('This command does not support the application kernel: "%s" does not extend "%s".', get_debug_type($kernel), Kernel::class)); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php index 7ac33243bb0c3..185278a662e1c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php @@ -43,7 +43,7 @@ public function __construct(string $name = null, FileLinkFormatter $fileLinkForm parent::__construct($name); } - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -77,9 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($search = $input->getArgument('search')) { $searchNormalized = preg_replace('/[^a-zA-Z0-9\x7f-\xff $]++/', '', $search); - $serviceIds = array_filter($serviceIds, function ($serviceId) use ($searchNormalized) { - return false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.'); - }); + $serviceIds = array_filter($serviceIds, fn ($serviceId) => false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.')); if (!$serviceIds) { $errorIo->error(sprintf('No autowirable classes or interfaces found matching "%s"', $search)); @@ -124,7 +122,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($builder->hasAlias($serviceId)) { $hasAlias[$serviceId] = true; $serviceAlias = $builder->getAlias($serviceId); - $serviceLine .= ' ('.$serviceAlias.')'; + + if ($builder->hasDefinition($serviceAlias) && $decorated = $builder->getDefinition($serviceAlias)->getTag('container.decorator')) { + $serviceLine .= ' ('.$decorated[0]['id'].')'; + } else { + $serviceLine .= ' ('.$serviceAlias.')'; + } if ($serviceAlias->isDeprecated()) { $serviceLine .= ' - deprecated'; @@ -132,6 +135,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int } elseif (!$all) { ++$serviceIdsNb; continue; + } elseif ($builder->getDefinition($serviceId)->isDeprecated()) { + $serviceLine .= ' - deprecated'; } $text[] = $serviceLine; $io->text($text); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php index 41edb7505ab5c..1a74e86824548 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php @@ -46,13 +46,13 @@ public function __construct(ContainerInterface $dispatchers) $this->dispatchers = $dispatchers; } - protected function configure() + protected function configure(): void { $this ->setDefinition([ new InputArgument('event', InputArgument::OPTIONAL, 'An event name or a part of the event name'), new InputOption('dispatcher', null, InputOption::VALUE_REQUIRED, 'To view events of a specific event dispatcher', self::DEFAULT_DISPATCHER), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), ]) ->setHelp(<<<'EOF' @@ -138,7 +138,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues((new DescriptorHelper())->getFormats()); + $suggestions->suggestValues($this->getAvailableFormatOptions()); } } @@ -155,4 +155,9 @@ private function searchForEvent(EventDispatcherInterface $dispatcher, string $ne return $output; } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index a4c53beff3957..27d16e636d9c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -50,13 +50,13 @@ public function __construct(RouterInterface $router, FileLinkFormatter $fileLink $this->fileLinkFormatter = $fileLinkFormatter; } - protected function configure() + protected function configure(): void { $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'A route name'), new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'), ]) ->setHelp(<<<'EOF' @@ -80,9 +80,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $routes = $this->router->getRouteCollection(); $container = null; if ($this->fileLinkFormatter) { - $container = function () { - return $this->getContainerBuilder($this->getApplication()->getKernel()); - }; + $container = fn () => $this->getContainerBuilder($this->getApplication()->getKernel()); } if ($name) { @@ -151,8 +149,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } if ($input->mustSuggestOptionValuesFor('format')) { - $helper = new DescriptorHelper(); - $suggestions->suggestValues($helper->getFormats()); + $suggestions->suggestValues($this->getAvailableFormatOptions()); } } @@ -167,4 +164,9 @@ private function findRouteContaining(string $name, RouteCollection $routes): Rou return $foundRoutes; } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php index a654522d806f2..7efd1f3ed3708 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php @@ -47,7 +47,7 @@ public function __construct(RouterInterface $router, iterable $expressionLanguag $this->expressionLanguageProviders = $expressionLanguageProviders; } - protected function configure() + protected function configure(): void { $this ->setDefinition([ diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php index 823c0f10d8d1a..22be8950244de 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php @@ -39,7 +39,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu parent::__construct(); } - protected function configure() + protected function configure(): void { $this ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force overriding of secrets that already exist in the local vault') diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php index aa5d25fc8dc2d..4c613ef7b27b6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php @@ -38,7 +38,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu parent::__construct(); } - protected function configure() + protected function configure(): void { $this ->setHelp(<<<'EOF' diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php index 40816665781bf..761f6c260cd7b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php @@ -41,7 +41,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu parent::__construct(); } - protected function configure() + protected function configure(): void { $this ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php index a9d31e2217967..8422d2c91a023 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php @@ -42,7 +42,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu parent::__construct(); } - protected function configure() + protected function configure(): void { $this ->addOption('reveal', 'r', InputOption::VALUE_NONE, 'Display decrypted values alongside names') @@ -75,9 +75,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $rows = []; $dump = new Dumper($output); - $dump = static function (?string $v) use ($dump) { - return null === $v ? '******' : $dump($v); - }; + $dump = static fn (?string $v) => null === $v ? '******' : $dump($v); foreach ($secrets as $name => $value) { $rows[$name] = [$name, $dump($value)]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php index dfbfcf2267469..e03afcd0cf902 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php @@ -43,7 +43,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu parent::__construct(); } - protected function configure() + protected function configure(): void { $this ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php index 1a0a500d89872..0e831a343d2f7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php @@ -44,7 +44,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu parent::__construct(); } - protected function configure() + protected function configure(): void { $this ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index f255053db3d5b..70d3a13e1db84 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -73,7 +73,7 @@ public function __construct(TranslatorInterface $translator, TranslationReaderIn $this->enabledLocales = $enabledLocales; } - protected function configure() + protected function configure(): void { $this ->setDefinition([ diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 7a4f1d97e7b79..15c536ea98a92 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -75,7 +75,7 @@ public function __construct(TranslationWriterInterface $writer, TranslationReade $this->enabledLocales = $enabledLocales; } - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -144,7 +144,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $format = $input->getOption('format'); $xliffVersion = '1.2'; - if (\in_array($format, array_keys(self::FORMATS), true)) { + if (\array_key_exists($format, self::FORMATS)) { [$format, $xliffVersion] = self::FORMATS[$format]; } @@ -232,12 +232,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $list = array_merge( array_diff($allKeys, $newKeys), - array_map(function ($id) { - return sprintf('%s', $id); - }, $newKeys), - array_map(function ($id) { - return sprintf('%s', $id); - }, array_keys($operation->getObsoleteMessages($domain))) + array_map(fn ($id) => sprintf('%s', $id), $newKeys), + array_map(fn ($id) => sprintf('%s', $id), array_keys($operation->getObsoleteMessages($domain))) ); $domainMessagesCount = \count($list); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php index 3daa2b9ddb71a..fa3c9b284bf06 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @@ -66,7 +66,7 @@ public function __construct($workflows) } } - protected function configure() + protected function configure(): void { $this ->setDefinition([ @@ -91,8 +91,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int { $workflowName = $input->getArgument('name'); - $workflow = null; - if (isset($this->workflows)) { if (!$this->workflows->has($workflowName)) { throw new InvalidArgumentException(sprintf('The workflow named "%s" cannot be found.', $workflowName)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php index 0c33e2b8b9c84..73d55e7506fd4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php @@ -36,14 +36,12 @@ public function __construct() return $default($directory); }; - $isReadableProvider = function ($fileOrDirectory, $default) { - return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); - }; + $isReadableProvider = fn ($fileOrDirectory, $default) => str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } - protected function configure() + protected function configure(): void { parent::configure(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php index 42c1e795ccee6..14139081264fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php @@ -35,14 +35,12 @@ public function __construct() return $default($directory); }; - $isReadableProvider = function ($fileOrDirectory, $default) { - return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); - }; + $isReadableProvider = fn ($fileOrDirectory, $default) => str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } - protected function configure() + protected function configure(): void { parent::configure(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index d73f323af7bbc..cdbb90a3a967a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -52,6 +52,9 @@ public function getKernel(): KernelInterface return $this->kernel; } + /** + * @return void + */ public function reset() { if ($this->kernel->getContainer()->has('services_resetter')) { @@ -137,6 +140,9 @@ public function add(Command $command): ?Command return parent::add($command); } + /** + * @return void + */ protected function registerCommands() { if ($this->commandsRegistered) { @@ -177,7 +183,7 @@ protected function registerCommands() } } - private function renderRegistrationErrors(InputInterface $input, OutputInterface $output) + private function renderRegistrationErrors(InputInterface $input, OutputInterface $output): void { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php index 09128274d9903..24b1545f104bf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php @@ -38,7 +38,7 @@ abstract class Descriptor implements DescriptorInterface */ protected $output; - public function describe(OutputInterface $output, mixed $object, array $options = []) + public function describe(OutputInterface $output, mixed $object, array $options = []): void { $this->output = $output; @@ -73,18 +73,18 @@ protected function getOutput(): OutputInterface return $this->output; } - protected function write(string $content, bool $decorated = false) + protected function write(string $content, bool $decorated = false): void { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } - abstract protected function describeRouteCollection(RouteCollection $routes, array $options = []); + abstract protected function describeRouteCollection(RouteCollection $routes, array $options = []): void; - abstract protected function describeRoute(Route $route, array $options = []); + abstract protected function describeRoute(Route $route, array $options = []): void; - abstract protected function describeContainerParameters(ParameterBag $parameters, array $options = []); + abstract protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void; - abstract protected function describeContainerTags(ContainerBuilder $builder, array $options = []); + abstract protected function describeContainerTags(ContainerBuilder $builder, array $options = []): void; /** * Describes a container service by its name. @@ -94,7 +94,7 @@ abstract protected function describeContainerTags(ContainerBuilder $builder, arr * * @param Definition|Alias|object $service */ - abstract protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null); + abstract protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null): void; /** * Describes container services. @@ -102,17 +102,17 @@ abstract protected function describeContainerService(object $service, array $opt * Common options are: * * tag: filters described services by given tag */ - abstract protected function describeContainerServices(ContainerBuilder $builder, array $options = []); + abstract protected function describeContainerServices(ContainerBuilder $builder, array $options = []): void; abstract protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void; - abstract protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null); + abstract protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null): void; - abstract protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null); + abstract protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null): void; - abstract protected function describeContainerParameter(mixed $parameter, array $options = []); + abstract protected function describeContainerParameter(mixed $parameter, array $options = []): void; - abstract protected function describeContainerEnvVars(array $envs, array $options = []); + abstract protected function describeContainerEnvVars(array $envs, array $options = []): void; /** * Describes event dispatcher listeners. @@ -120,9 +120,9 @@ abstract protected function describeContainerEnvVars(array $envs, array $options * Common options are: * * name: name of listened event */ - abstract protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []); + abstract protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void; - abstract protected function describeCallable(mixed $callable, array $options = []); + abstract protected function describeCallable(mixed $callable, array $options = []): void; protected function formatValue(mixed $value): string { @@ -214,7 +214,7 @@ protected function findDefinitionsByTag(ContainerBuilder $builder, bool $showHid return $definitions; } - protected function sortParameters(ParameterBag $parameters) + protected function sortParameters(ParameterBag $parameters): array { $parameters = $parameters->all(); ksort($parameters); @@ -222,7 +222,7 @@ protected function sortParameters(ParameterBag $parameters) return $parameters; } - protected function sortServiceIds(array $serviceIds) + protected function sortServiceIds(array $serviceIds): array { asort($serviceIds); @@ -241,9 +241,7 @@ protected function sortTaggedServicesByPriority(array $services): array } } } - uasort($maxPriority, function ($a, $b) { - return $b <=> $a; - }); + uasort($maxPriority, fn ($a, $b) => $b <=> $a); return array_keys($maxPriority); } @@ -260,9 +258,7 @@ protected function sortTagsByPriority(array $tags): array protected function sortByPriority(array $tag): array { - usort($tag, function ($a, $b) { - return ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0); - }); + usort($tag, fn ($a, $b) => ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0)); return $tag; } @@ -296,7 +292,7 @@ private function getContainerEnvVars(ContainerBuilder $container): array return []; } - if (!is_file($container->getParameter('debug.container.dump'))) { + if (!$container->getParameter('debug.container.dump') || !is_file($container->getParameter('debug.container.dump'))) { return []; } @@ -305,9 +301,7 @@ private function getContainerEnvVars(ContainerBuilder $container): array $envVars = array_unique($envVars[1]); $bag = $container->getParameterBag(); - $getDefaultParameter = function (string $name) { - return parent::get($name); - }; + $getDefaultParameter = fn (string $name) => parent::get($name); $getDefaultParameter = $getDefaultParameter->bindTo($bag, $bag::class); $getEnvReflection = new \ReflectionMethod($container, 'getEnv'); @@ -343,9 +337,10 @@ private function getContainerEnvVars(ContainerBuilder $container): array protected function getServiceEdges(ContainerBuilder $builder, string $serviceId): array { try { - return array_values(array_unique(array_map(function (ServiceReferenceGraphEdge $edge) { - return $edge->getSourceNode()->getId(); - }, $builder->getCompiler()->getServiceReferenceGraph()->getNode($serviceId)->getInEdges()))); + return array_values(array_unique(array_map( + fn (ServiceReferenceGraphEdge $edge) => $edge->getSourceNode()->getId(), + $builder->getCompiler()->getServiceReferenceGraph()->getNode($serviceId)->getInEdges() + ))); } catch (InvalidArgumentException $exception) { return []; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index c454b85ffb4bf..5806fd32f8ad8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -32,7 +32,7 @@ */ class JsonDescriptor extends Descriptor { - protected function describeRouteCollection(RouteCollection $routes, array $options = []) + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void { $data = []; foreach ($routes->all() as $name => $route) { @@ -42,17 +42,17 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio $this->writeData($data, $options); } - protected function describeRoute(Route $route, array $options = []) + protected function describeRoute(Route $route, array $options = []): void { $this->writeData($this->getRouteData($route), $options); } - protected function describeContainerParameters(ParameterBag $parameters, array $options = []) + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void { $this->writeData($this->sortParameters($parameters), $options); } - protected function describeContainerTags(ContainerBuilder $builder, array $options = []) + protected function describeContainerTags(ContainerBuilder $builder, array $options = []): void { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $data = []; @@ -67,7 +67,7 @@ protected function describeContainerTags(ContainerBuilder $builder, array $optio $this->writeData($data, $options); } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -82,7 +82,7 @@ protected function describeContainerService(object $service, array $options = [] } } - protected function describeContainerServices(ContainerBuilder $builder, array $options = []) + protected function describeContainerServices(ContainerBuilder $builder, array $options = []): void { $serviceIds = isset($options['tag']) && $options['tag'] ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag'])) @@ -115,12 +115,12 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $this->writeData($data, $options); } - protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null): void { $this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $builder, $options['id'] ?? null), $options); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null): void { if (!$builder) { $this->writeData($this->getContainerAliasData($alias), $options); @@ -134,24 +134,24 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con ); } - protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void { $this->writeData($this->getEventDispatcherListenersData($eventDispatcher, $options), $options); } - protected function describeCallable(mixed $callable, array $options = []) + protected function describeCallable(mixed $callable, array $options = []): void { $this->writeData($this->getCallableData($callable), $options); } - protected function describeContainerParameter(mixed $parameter, array $options = []) + protected function describeContainerParameter(mixed $parameter, array $options = []): void { $key = $options['parameter'] ?? ''; $this->writeData([$key => $parameter], $options); } - protected function describeContainerEnvVars(array $envs, array $options = []) + protected function describeContainerEnvVars(array $envs, array $options = []): void { throw new LogicException('Using the JSON format to debug environment variables is not supported.'); } @@ -180,7 +180,7 @@ protected function describeContainerDeprecations(ContainerBuilder $builder, arra $this->writeData(['remainingCount' => $remainingCount, 'deprecations' => $formattedLogs], $options); } - private function writeData(array $data, array $options) + private function writeData(array $data, array $options): void { $flags = $options['json_encoding'] ?? 0; @@ -304,7 +304,7 @@ private function getEventDispatcherListenersData(EventDispatcherInterface $event $data[] = $l; } } else { - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { @@ -328,7 +328,7 @@ private function getCallableData(mixed $callable): array if (\is_object($callable[0])) { $data['name'] = $callable[1]; - $data['class'] = \get_class($callable[0]); + $data['class'] = $callable[0]::class; } else { if (!str_starts_with($callable[1], 'parent::')) { $data['name'] = $callable[1]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 36b297172681f..4581e0e198b99 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -29,7 +29,7 @@ */ class MarkdownDescriptor extends Descriptor { - protected function describeRouteCollection(RouteCollection $routes, array $options = []) + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void { $first = true; foreach ($routes->all() as $name => $route) { @@ -43,7 +43,7 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio $this->write("\n"); } - protected function describeRoute(Route $route, array $options = []) + protected function describeRoute(Route $route, array $options = []): void { $output = '- Path: '.$route->getPath() ."\n".'- Path Regex: '.$route->compile()->getRegex() @@ -66,7 +66,7 @@ protected function describeRoute(Route $route, array $options = []) $this->write("\n"); } - protected function describeContainerParameters(ParameterBag $parameters, array $options = []) + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void { $this->write("Container parameters\n====================\n"); foreach ($this->sortParameters($parameters) as $key => $value) { @@ -74,7 +74,7 @@ protected function describeContainerParameters(ParameterBag $parameters, array $ } } - protected function describeContainerTags(ContainerBuilder $builder, array $options = []) + protected function describeContainerTags(ContainerBuilder $builder, array $options = []): void { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $this->write("Container tags\n=============="); @@ -88,7 +88,7 @@ protected function describeContainerTags(ContainerBuilder $builder, array $optio } } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -132,7 +132,7 @@ protected function describeContainerDeprecations(ContainerBuilder $builder, arra } } - protected function describeContainerServices(ContainerBuilder $builder, array $options = []) + protected function describeContainerServices(ContainerBuilder $builder, array $options = []): void { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; @@ -193,7 +193,7 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o } } - protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null): void { $output = ''; @@ -263,7 +263,7 @@ protected function describeContainerDefinition(Definition $definition, array $op $this->write(isset($options['id']) ? sprintf("### %s\n\n%s\n", $options['id'], $output) : $output); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null): void { $output = '- Service: `'.$alias.'`' ."\n".'- Public: '.($alias->isPublic() && !$alias->isPrivate() ? 'yes' : 'no'); @@ -284,17 +284,17 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con $this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias]), $builder); } - protected function describeContainerParameter(mixed $parameter, array $options = []) + protected function describeContainerParameter(mixed $parameter, array $options = []): void { $this->write(isset($options['parameter']) ? sprintf("%s\n%s\n\n%s", $options['parameter'], str_repeat('=', \strlen($options['parameter'])), $this->formatParameter($parameter)) : $parameter); } - protected function describeContainerEnvVars(array $envs, array $options = []) + protected function describeContainerEnvVars(array $envs, array $options = []): void { throw new LogicException('Using the markdown format to debug environment variables is not supported.'); } - protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void { $event = $options['event'] ?? null; $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; @@ -310,7 +310,7 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev $registeredListeners = $eventDispatcher->getListeners($event); } else { // Try to see if "events" exists - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array 10000 _key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); } $this->write(sprintf('# %s', $title)."\n"); @@ -336,7 +336,7 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev } } - protected function describeCallable(mixed $callable, array $options = []) + protected function describeCallable(mixed $callable, array $options = []): void { $string = ''; @@ -345,7 +345,7 @@ protected function describeCallable(mixed $callable, array $options = []) if (\is_object($callable[0])) { $string .= "\n".sprintf('- Name: `%s`', $callable[1]); - $string .= "\n".sprintf('- Class: `%s`', \get_class($callable[0])); + $string .= "\n".sprintf('- Class: `%s`', $callable[0]::class); } else { if (!str_starts_with($callable[1], 'parent::')) { $string .= "\n".sprintf('- Name: `%s`', $callable[1]); @@ -359,7 +359,9 @@ protected function describeCallable(mixed $callable, array $options = []) } } - return $this->write($string."\n"); + $this->write($string."\n"); + + return; } if (\is_string($callable)) { @@ -375,7 +377,9 @@ protected function describeCallable(mixed $callable, array $options = []) $string .= "\n- Static: yes"; } - return $this->write($string."\n"); + $this->write($string."\n"); + + return; } if ($callable instanceof \Closure) { @@ -383,7 +387,9 @@ protected function describeCallable(mixed $callable, array $options = []) $r = new \ReflectionFunction($callable); if (str_contains($r->name, '{closure}')) { - return $this->write($string."\n"); + $this->write($string."\n"); + + return; } $string .= "\n".sprintf('- Name: `%s`', $r->name); @@ -394,14 +400,18 @@ protected function describeCallable(mixed $callable, array $options = []) } } - return $this->write($string."\n"); + $this->write($string."\n"); + + return; } if (method_exists($callable, '__invoke')) { $string .= "\n- Type: `object`"; $string .= "\n".sprintf('- Name: `%s`', $callable::class); - return $this->write($string."\n"); + $this->write($string."\n"); + + return; } throw new \InvalidArgumentException('Callable is not describable.'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index e589f7b3400a8..f555e7220901c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -44,7 +44,7 @@ public function __construct(FileLinkFormatter $fileLinkFormatter = null) $this->fileLinkFormatter = $fileLinkFormatter; } - protected function describeRouteCollection(RouteCollection $routes, array $options = []) + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void { $showControllers = isset($options['show_controllers']) && $options['show_controllers']; @@ -81,7 +81,7 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio } } - protected function describeRoute(Route $route, array $options = []) + protected function describeRoute(Route $route, array $options = []): void { $defaults = $route->getDefaults(); if (isset($defaults['_controller'])) { @@ -112,7 +112,7 @@ protected function describeRoute(Route $route, array $options = []) $table->render(); } - protected function describeContainerParameters(ParameterBag $parameters, array $options = []) + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void { $tableHeaders = ['Parameter', 'Value']; @@ -125,7 +125,7 @@ protected function describeContainerParameters(ParameterBag $parameters, array $ $options['output']->table($tableHeaders, $tableRows); } - protected function describeContainerTags(ContainerBuilder $builder, array $options = []) + protected function describeContainerTags(ContainerBuilder $builder, array $options = []): void { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; @@ -141,7 +141,7 @@ protected function describeContainerTags(ContainerBuilder $builder, array $optio } } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -162,7 +162,7 @@ protected function describeContainerService(object $service, array $options = [] } } - protected function describeContainerServices(ContainerBuilder $builder, array $options = []) + protected function describeContainerServices(ContainerBuilder $builder, array $options = []): void { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $showTag = $options['tag'] ?? null; @@ -251,7 +251,7 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $options['output']->table($tableHeaders, $tableRows); } - protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null): void { if (isset($options['id'])) { $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); @@ -271,9 +271,7 @@ protected function describeContainerDefinition(Definition $definition, array $op $tagInformation = []; foreach ($tags as $tagName => $tagData) { foreach ($tagData as $tagParameters) { - $parameters = array_map(function ($key, $value) { - return sprintf('%s: %s', $key, $value); - }, array_keys($tagParameters), array_values($tagParameters)); + $parameters = array_map(fn ($key, $value) => sprintf('%s: %s', $key, $value), array_keys($tagParameters), array_values($tagParameters)); $parameters = implode(', ', $parameters); if ('' === $parameters) { @@ -392,7 +390,7 @@ protected function describeContainerDeprecations(ContainerBuilder $builder, arra $options['output']->listing($formattedLogs); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null): void { if ($alias->isPublic() && !$alias->isPrivate()) { $options['output']->comment(sprintf('This service is a public alias for the service %s', (string) $alias)); @@ -407,7 +405,7 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con $this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias]), $builder); } - protected function describeContainerParameter(mixed $parameter, array $options = []) + protected function describeContainerParameter(mixed $parameter, array $options = []): void { $options['output']->table( ['Parameter', 'Value'], @@ -417,7 +415,7 @@ protected function describeContainerParameter(mixed $parameter, array $options = ]); } - protected function describeContainerEnvVars(array $envs, array $options = []) + protected function describeContainerEnvVars(array $envs, array $options = []): void { $dump = new Dumper($this->output); $options['output']->title('Symfony Container Environment Variables'); @@ -479,7 +477,7 @@ protected function describeContainerEnvVars(array $envs, array $options = []) } } - protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void { $event = $options['event'] ?? null; $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; @@ -496,7 +494,7 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev } else { $title .= ' Grouped by Event'; // Try to see if "events" exists - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); } $options['output']->title($title); @@ -511,12 +509,12 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev } } - protected function describeCallable(mixed $callable, array $options = []) + protected function describeCallable(mixed $callable, array $options = []): void { $this->writeText($this->formatCallable($callable), $options); } - private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, string $event, array $eventListeners, SymfonyStyle $io) + private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, string $event, array $eventListeners, SymfonyStyle $io): void { $tableHeaders = ['Order', 'Callable', 'Priority']; $tableRows = []; @@ -602,7 +600,7 @@ private function formatCallable(mixed $callable): string { if (\is_array($callable)) { if (\is_object($callable[0])) { - return sprintf('%s::%s()', \get_class($callable[0]), $callable[1]); + return sprintf('%s::%s()', $callable[0]::class, $callable[1]); } return sprintf('%s::%s()', $callable[0], $callable[1]); @@ -631,7 +629,7 @@ private function formatCallable(mixed $callable): string throw new \InvalidArgumentException('Callable is not describable.'); } - private function writeText(string $content, array $options = []) + private function writeText(string $content, array $options = []): void { $this->write( isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index 6c01c15ce04db..17870bb96a69b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -33,27 +33,27 @@ */ class XmlDescriptor extends Descriptor { - protected function describeRouteCollection(RouteCollection $routes, array $options = []) + protected function describeRouteCollection(RouteCollection $routes, array $options = []): void { $this->writeDocument($this->getRouteCollectionDocument($routes)); } - protected function describeRoute(Route $route, array $options = []) + protected function describeRoute(Route $route, array $options = []): void { $this->writeDocument($this->getRouteDocument($route, $options['name'] ?? null)); } - protected function describeContainerParameters(ParameterBag $parameters, array $options = []) + protected function describeContainerParameters(ParameterBag $parameters, array $options = []): void { $this->writeDocument($this->getContainerParametersDocument($parameters)); } - protected function describeContainerTags(ContainerBuilder $builder, array $options = []) + protected function describeContainerTags(ContainerBuilder $builder, array $options = []): void { $this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_hidden']) && $options['show_hidden'])); } - protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null): void { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -62,17 +62,17 @@ protected function describeContainerService(object $service, array $options = [] $this->writeDocument($this->getContainerServiceDocument($service, $options['id'], $builder, isset($options['show_arguments']) && $options['show_arguments'])); } - protected function describeContainerServices(ContainerBuilder $builder, array $options = []) + protected function describeContainerServices(ContainerBuilder $builder, array $options = []): void { $this->writeDocument($this->getContainerServicesDocument($builder, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null, $options['id'] ?? null)); } - protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null): void { $this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $builder)); } - protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null): void { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($dom->importNode($this->getContainerAliasDocument($alias, $options['id'] ?? null)->childNodes->item(0), true)); @@ -88,22 +88,22 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con $this->writeDocument($dom); } - protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []): void { $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, $options)); } - protected function describeCallable(mixed $callable, array $options = []) + protected function describeCallable(mixed $callable, array $options = []): void { $this->writeDocument($this->getCallableDocument($callable)); } - protected function describeContainerParameter(mixed $parameter, array $options = []) + protected function describeContainerParameter(mixed $parameter, array $options = []): void { $this->writeDocument($this->getContainerParameterDocument($parameter, $options)); } - protected function describeContainerEnvVars(array $envs, array $options = []) + protected function describeContainerEnvVars(array $envs, array $options = []): void { throw new LogicException('Using the XML format to debug environment variables is not supported.'); } @@ -120,7 +120,6 @@ protected function describeContainerDeprecations(ContainerBuilder $builder, arra $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($deprecationsXML = $dom->createElement('deprecations')); - $formattedLogs = []; $remainingCount = 0; foreach ($logs as $log) { $deprecationsXML->appendChild($deprecationXML = $dom->createElement('deprecation')); @@ -136,7 +135,7 @@ protected function describeContainerDeprecations(ContainerBuilder $builder, arra $this->writeDocument($dom); } - private function writeDocument(\DOMDocument $dom) + private function writeDocument(\DOMDocument $dom): void { $dom->formatOutput = true; $this->write($dom->saveXML()); @@ -489,7 +488,7 @@ private function getEventDispatcherListenersDocument(EventDispatcherInterface $e $this->appendEventListenerDocument($eventDispatcher, $event, $eventDispatcherXML, $registeredListeners); } else { // Try to see if "events" exists - $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(fn ($event) => $eventDispatcher->getListeners($event), $options['events'])) : $eventDispatcher->getListeners(); ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { @@ -503,7 +502,7 @@ private function getEventDispatcherListenersDocument(EventDispatcherInterface $e return $dom; } - private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, string $event, \DOMElement $element, array $eventListeners) + private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, string $event, \DOMElement $element, array $eventListeners): void { foreach ($eventListeners as $listener) { $callableXML = $this->getCallableDocument($listener); @@ -523,7 +522,7 @@ private function getCallableDocument(mixed $callable): \DOMDocument if (\is_object($callable[0])) { $callableXML->setAttribute('name', $callable[1]); - $callableXML->setAttribute('class', \get_class($callable[0])); + $callableXML->setAttribute('class', $callable[0]::class); } else { if (!str_starts_with($callable[1], 'parent::')) { $callableXML->setAttribute('name', $callable[1]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index d195d20ce0353..6d39e9440f02a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -12,7 +12,9 @@ namespace Symfony\Bundle\FrameworkBundle\Controller; use Psr\Container\ContainerInterface; +use Psr\Link\EvolvableLinkInterface; use Psr\Link\LinkInterface; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; use Symfony\Component\Form\Extension\Core\Type\FormType; @@ -42,6 +44,8 @@ use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Component\WebLink\Link; use Symfony\Contracts\Service\Attribute\Required; use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; @@ -58,9 +62,6 @@ abstract class AbstractController implements ServiceSubscriberInterface */ protected $container; - /** - * @required - */ #[Required] public function setContainer(ContainerInterface $container): ?ContainerInterface { @@ -95,6 +96,8 @@ public static function getSubscribedServices(): array 'security.token_storage' => '?'.TokenStorageInterface::class, 'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class, 'parameter_bag' => '?'.ContainerBagInterface::class, + 'web_link.http_header_serializer' => '?'.HttpHeaderSerializer::class, + 'asset_mapper.importmap.manager' => '?'.ImportMapManager::class, ]; } @@ -166,7 +169,7 @@ protected function json(mixed $data, int $status = 200, array $headers = [], arr protected function file(\SplFileInfo|string $file, string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse { $response = new BinaryFileResponse($file); - $response->setContentDisposition($disposition, null === $fileName ? $response->getFile()->getFilename() : $fileName); + $response->setContentDisposition($disposition, $fileName ?? $response->getFile()->getFilename()); return $response; } @@ -405,4 +408,41 @@ protected function addLink(Request $request, LinkInterface $link): void $request->attributes->set('_links', $linkProvider->withLink($link)); } + + /** + * @param LinkInterface[] $links + */ + protected function sendEarlyHints(iterable $links = [], Response $response = null, bool $preloadJavaScriptModules = false): Response + { + if (!$this->container->has('web_link.http_header_serializer')) { + throw new \LogicException('You cannot use the "sendEarlyHints" method if the WebLink component is not available. Try running "composer require symfony/web-link".'); + } + + $response ??= new Response(); + + $populatedLinks = []; + + if ($preloadJavaScriptModules) { + if (!$this->container->has('asset_mapper.importmap.manager')) { + throw new \LogicException('You cannot use the JavaScript modules method if the AssetMapper component is not available. Try running "composer require symfony/asset-mapper".'); + } + + foreach ($this->container->get('asset_mapper.importmap.manager')->getModulesToPreload() as $url) { + $populatedLinks[] = new Link('modulepreload', $url); + } + } + + foreach ($links as $link) { + if ($link instanceof EvolvableLinkInterface && !$link->getRels()) { + $link = $link->withRel('preload'); + } + + $populatedLinks[] = $link; + } + + $response->headers->set('Link', $this->container->get('web_link.http_header_serializer')->serialize($populatedLinks), false); + $response->sendHeaders(103); + + return $response; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php index 3d754bb7d0b1e..437458a499255 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php @@ -43,7 +43,7 @@ public function __construct(Environment $twig = null) public function templateAction(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response { if (null === $this->twig) { - throw new \LogicException('You cannot use the TemplateController if the Twig Bundle is not available.'); + throw new \LogicException('You cannot use the TemplateController if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); } $response = new Response($this->twig->render($template, $context), $statusCode); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php index fbbf53d3431f2..2105a54df9f36 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php @@ -20,7 +20,7 @@ */ class AddAnnotationsCachedReaderPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { // "annotations.cached_reader" is wired late so that any passes using // "annotation_reader" at build time don't get any cache diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php index a1bf0a80eadb0..d0aca7a06bf06 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php @@ -17,6 +17,9 @@ class AddDebugLogProcessorPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('profiler')) { @@ -34,6 +37,9 @@ public function process(ContainerBuilder $container) $definition->addMethodCall('pushProcessor', [new Reference('debug.log_processor')]); } + /** + * @return void + */ public static function configureLogger(mixed $logger) { if (\is_object($logger) && method_exists($logger, 'removeDebugLogger') && \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php index f7a6bcdbd710a..5442b277348e3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php @@ -22,6 +22,9 @@ */ class AddExpressionLanguageProvidersPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { // routing diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AssetsContextPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AssetsContextPass.php index 3fc79f0ee0d64..e8c2ad3a0e031 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AssetsContextPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AssetsContextPass.php @@ -18,6 +18,9 @@ class AssetsContextPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('assets.context')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php index 0df5420c769ee..1e08ef314941a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php @@ -25,8 +25,15 @@ */ class ContainerBuilderDebugDumpPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { + if (!$container->getParameter('debug.container.dump')) { + return; + } + $cache = new ConfigCache($container->getParameter('debug.container.dump'), true); if (!$cache->isFresh()) { $cache->write((new XmlDumper($container))->dump(), $container->getResources()); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php index 898f961d0a1f3..e66e98b451d6c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php @@ -20,6 +20,9 @@ */ class DataCollectorTranslatorPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->has('translator')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/EnableLoggerDebugModePass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/EnableLoggerDebugModePass.php index 3dad9d6b24031..ef303a1406da9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/EnableLoggerDebugModePass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/EnableLoggerDebugModePass.php @@ -17,7 +17,7 @@ final class EnableLoggerDebugModePass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('profiler') || !$container->hasDefinition('logger')) { return; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php index 80cbe52e6c4f4..b7cb920bf7434 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php @@ -22,6 +22,9 @@ */ class LoggingTranslatorPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasAlias('logger') || !$container->hasAlias('translator')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ProfilerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ProfilerPass.php index 7200b12b9ba87..c2d669fe1cf3a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ProfilerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ProfilerPass.php @@ -24,6 +24,9 @@ */ class ProfilerPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (false === $container->hasDefinition('profiler')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php index 8b6479c4f2edd..fedc30d06eec4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php @@ -19,6 +19,9 @@ */ class RemoveUnusedSessionMarshallingHandlerPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('session.marshalling_handler')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php index 942eb635b26f3..09f272daa940e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php @@ -21,6 +21,9 @@ */ class TestServiceContainerRealRefPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('test.private_services_locator')) { @@ -30,10 +33,14 @@ public function process(ContainerBuilder $container) $privateContainer = $container->getDefinition('test.private_services_locator'); $definitions = $container->getDefinitions(); $privateServices = $privateContainer->getArgument(0); + $renamedIds = []; foreach ($privateServices as $id => $argument) { if (isset($definitions[$target = (string) $argument->getValues()[0]])) { $argument->setValues([new Reference($target)]); + if ($id !== $target) { + $renamedIds[$id] = $target; + } } else { unset($privateServices[$id]); } @@ -47,8 +54,14 @@ public function process(ContainerBuilder $container) if ($definitions[$target]->hasTag('container.private')) { $privateServices[$id] = new ServiceClosureArgument(new Reference($target)); } + + $renamedIds[$id] = $target; } $privateContainer->replaceArgument(0, $privateServices); + + if ($container->hasDefinition('test.service_container') && $renamedIds) { + $container->getDefinition('test.service_container')->setArgument(2, $renamedIds); + } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php index a68f94f7b6134..6e7669a710eb2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php @@ -14,7 +14,6 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; /** @@ -22,6 +21,9 @@ */ class TestServiceContainerWeakRefPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('test.private_services_locator')) { @@ -30,10 +32,9 @@ public function process(ContainerBuilder $container) $privateServices = []; $definitions = $container->getDefinitions(); - $hasErrors = method_exists(Definition::class, 'hasErrors') ? 'hasErrors' : 'getErrors'; foreach ($definitions as $id => $definition) { - if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) && !$definition->$hasErrors() && !$definition->isAbstract()) { + if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) && !$definition->hasErrors() && !$definition->isAbstract()) { $privateServices[$id] = new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); } } @@ -45,7 +46,7 @@ public function process(ContainerBuilder $container) while (isset($aliases[$target = (string) $alias])) { $alias = $aliases[$target]; } - if (isset($definitions[$target]) && !$definitions[$target]->$hasErrors() && !$definitions[$target]->isAbstract()) { + if (isset($definitions[$target]) && !$definitions[$target]->hasErrors() && !$definitions[$target]->isAbstract()) { $privateServices[$id] = new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index f127f32b49952..a76b84ad8337c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -24,6 +24,7 @@ class UnusedTagsPass implements CompilerPassInterface private const KNOWN_TAGS = [ 'annotations.cached_reader', 'assets.package', + 'asset_mapper.compiler', 'auto_alias', 'cache.pool', 'cache.pool.clearer', @@ -46,6 +47,7 @@ class UnusedTagsPass implements CompilerPassInterface 'container.stack', 'controller.argument_value_resolver', 'controller.service_arguments', + 'controller.targeted_value_resolver', 'data_collector', 'event_dispatcher.dispatcher', 'form.type', @@ -74,11 +76,13 @@ class UnusedTagsPass implements CompilerPassInterface 'property_info.list_extractor', 'property_info.type_extractor', 'proxy', + 'remote_event.consumer', 'routing.condition_service', 'routing.expression_language_function', 'routing.expression_language_provider', 'routing.loader', 'routing.route_loader', + 'scheduler.schedule_provider', 'security.authenticator.login_linker', 'security.expression_language_provider', 'security.remember_me_aware', @@ -101,6 +105,9 @@ class UnusedTagsPass implements CompilerPassInterface 'workflow', ]; + /** + * @return void + */ public function process(ContainerBuilder $container) { $tags = array_unique(array_merge($container->findTags(), self::KNOWN_TAGS)); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php index d74fa11b2ae6b..bda9ca9515258 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php @@ -21,6 +21,9 @@ */ class WorkflowGuardListenerPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasParameter('workflow.has_guard_listeners')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 38ff0b68f82ed..d668d435a42e2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -16,6 +16,8 @@ use Psr\Log\LogLevel; use Symfony\Bundle\FullStack; use Symfony\Component\Asset\Package; +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeBuilder; @@ -36,11 +38,14 @@ use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Scheduler\Schedule; use Symfony\Component\Semaphore\Semaphore; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\Translator; use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Validator\Validation; +use Symfony\Component\Webhook\Controller\WebhookController; use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow\WorkflowEvents; @@ -144,9 +149,7 @@ public function getConfigTreeBuilder(): TreeBuilder return ContainerBuilder::willBeAvailable($package, $class, $parentPackages); }; - $enableIfStandalone = static function (string $package, string $class) use ($willBeAvailable) { - return !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled'; - }; + $enableIfStandalone = static fn (string $package, string $class) => !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled'; $this->addCsrfSection($rootNode); $this->addFormSection($rootNode, $enableIfStandalone); @@ -160,6 +163,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addSessionSection($rootNode); $this->addRequestSection($rootNode); $this->addAssetsSection($rootNode, $enableIfStandalone); + $this->addAssetMapperSection($rootNode, $enableIfStandalone); $this->addTranslatorSection($rootNode, $enableIfStandalone); $this->addValidationSection($rootNode, $enableIfStandalone); $this->addAnnotationsSection($rootNode, $willBeAvailable); @@ -173,6 +177,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addLockSection($rootNode, $enableIfStandalone); $this->addSemaphoreSection($rootNode, $enableIfStandalone); $this->addMessengerSection($rootNode, $enableIfStandalone); + $this->addSchedulerSection($rootNode, $enableIfStandalone); $this->addRobotsIndexSection($rootNode); $this->addHttpClientSection($rootNode, $enableIfStandalone); $this->addMailerSection($rootNode, $enableIfStandalone); @@ -181,11 +186,13 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addRateLimiterSection($rootNode, $enableIfStandalone); $this->addUidSection($rootNode, $enableIfStandalone); $this->addHtmlSanitizerSection($rootNode, $enableIfStandalone); + $this->addWebhookSection($rootNode, $enableIfStandalone); + $this->addRemoteEventSection($rootNode, $enableIfStandalone); return $treeBuilder; } - private function addSecretsSection(ArrayNodeDefinition $rootNode) + private function addSecretsSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -201,7 +208,7 @@ private function addSecretsSection(ArrayNodeDefinition $rootNode) ; } - private function addCsrfSection(ArrayNodeDefinition $rootNode) + private function addCsrfSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -219,7 +226,7 @@ private function addCsrfSection(ArrayNodeDefinition $rootNode) ; } - private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -246,7 +253,7 @@ private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableI ; } - private function addHttpCacheSection(ArrayNodeDefinition $rootNode) + private function addHttpCacheSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -265,6 +272,10 @@ private function addHttpCacheSection(ArrayNodeDefinition $rootNode) ->performNoDeepMerging() ->scalarPrototype()->end() ->end() + ->arrayNode('skip_response_headers') + ->performNoDeepMerging() + ->scalarPrototype()->end() + ->end() ->booleanNode('allow_reload')->end() ->booleanNode('allow_revalidate')->end() ->integerNode('stale_while_revalidate')->end() @@ -276,7 +287,7 @@ private function addHttpCacheSection(ArrayNodeDefinition $rootNode) ; } - private function addEsiSection(ArrayNodeDefinition $rootNode) + private function addEsiSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -288,7 +299,7 @@ private function addEsiSection(ArrayNodeDefinition $rootNode) ; } - private function addSsiSection(ArrayNodeDefinition $rootNode) + private function addSsiSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -299,7 +310,7 @@ private function addSsiSection(ArrayNodeDefinition $rootNode) ->end(); } - private function addFragmentsSection(ArrayNodeDefinition $rootNode) + private function addFragmentsSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -315,7 +326,7 @@ private function addFragmentsSection(ArrayNodeDefinition $rootNode) ; } - private function addProfilerSection(ArrayNodeDefinition $rootNode) + private function addProfilerSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -335,7 +346,7 @@ private function addProfilerSection(ArrayNodeDefinition $rootNode) ; } - private function addWorkflowSection(ArrayNodeDefinition $rootNode) + private function addWorkflowSection(ArrayNodeDefinition $rootNode): void { $rootNode ->fixXmlConfig('workflow') @@ -598,7 +609,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ; } - private function addRouterSection(ArrayNodeDefinition $rootNode) + private function addRouterSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -631,7 +642,7 @@ private function addRouterSection(ArrayNodeDefinition $rootNode) ; } - private function addSessionSection(ArrayNodeDefinition $rootNode) + private function addSessionSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -680,7 +691,7 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ; } - private function addRequestSection(ArrayNodeDefinition $rootNode) + private function addRequestSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -693,8 +704,8 @@ private function addRequestSection(ArrayNodeDefinition $rootNode) ->useAttributeAsKey('name') ->prototype('array') ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && isset($v['mime_type']); }) - ->then(function ($v) { return $v['mime_type']; }) + ->ifTrue(fn ($v) => \is_array($v) && isset($v['mime_type'])) + ->then(fn ($v) => $v['mime_type']) ->end() ->beforeNormalization()->castToArray()->end() ->prototype('scalar')->end() @@ -706,7 +717,7 @@ private function addRequestSection(ArrayNodeDefinition $rootNode) ; } - private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -802,7 +813,98 @@ private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enabl ; } - private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addAssetMapperSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('asset_mapper') + ->info('Asset Mapper configuration') + ->{$enableIfStandalone('symfony/asset-mapper', AssetMapper::class)}() + ->fixXmlConfig('path') + ->fixXmlConfig('extension') + ->fixXmlConfig('importmap_script_attribute') + ->children() + // add array node called "paths" that will be an array of strings + ->arrayNode('paths') + ->info('Directories that hold assets that should be in the mapper. Can be a simple array of an array of ["path/to/assets": "namespace"]') + ->example(['assets/']) + ->normalizeKeys(false) + ->useAttributeAsKey('namespace') + ->beforeNormalization() + ->always() + ->then(function ($v) { + $result = []; + foreach ($v as $key => $item) { + // "dir" => "namespace" + if (\is_string($key)) { + $result[$key] = $item; + + continue; + } + + if (\is_array($item)) { + // $item = ["namespace" => "the/namespace", "value" => "the/dir"] + $result[$item['value']] = $item['namespace'] ?? ''; + } else { + // $item = "the/dir" + $result[$item] = ''; + } + } + + return $result; + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->booleanNode('server') + ->info('If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default)') + ->defaultValue($this->debug) + ->end() + ->scalarNode('public_prefix') + ->info('The public path where the assets will be written to (and served from when "server" is true)') + ->defaultValue('/assets/') + ->end() + ->booleanNode('strict_mode') + ->info('If true, an exception will be thrown if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import \'./non-existent.js\'"') + ->defaultValue(true) + ->end() + ->arrayNode('extensions') + ->info('Key-value pair of file extensions set to their mime type.') + ->normalizeKeys(false) + ->useAttributeAsKey('extension') + ->example(['.zip' => 'application/zip']) + ->prototype('scalar')->end() + ->end() + ->scalarNode('importmap_path') + ->info('The path of the importmap.php file.') + ->defaultValue('%kernel.project_dir%/importmap.php') + ->end() + ->scalarNode('importmap_polyfill') + ->info('URL of the ES Module Polyfill to use, false to disable. Defaults to using a CDN URL.') + ->defaultValue(null) + ->end() + ->arrayNode('importmap_script_attributes') + ->info('Key-value pair of attributes to add to script tags output for the importmap.') + ->normalizeKeys(false) + ->useAttributeAsKey('key') + ->example(['data-turbo-track' => 'reload']) + ->prototype('scalar')->end() + ->end() + ->scalarNode('vendor_dir') + ->info('The directory to store JavaScript vendors.') + ->defaultValue('%kernel.project_dir%/assets/vendor') + ->end() + ->scalarNode('provider') + ->info('The provider (CDN) to use', class_exists(ImportMapManager::class) ? sprintf(' (e.g.: "%s").', implode('", "', ImportMapManager::PROVIDERS)) : '.') + ->defaultValue('jspm') + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -872,7 +974,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $e ; } - private function addValidationSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addValidationSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -962,7 +1064,7 @@ private function addValidationSection(ArrayNodeDefinition $rootNode, callable $e ; } - private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) + private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void { $rootNode ->children() @@ -982,7 +1084,7 @@ private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $ ; } - private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1015,7 +1117,7 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $e ; } - private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) + private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void { $rootNode ->children() @@ -1035,7 +1137,7 @@ private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callabl ; } - private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1047,7 +1149,7 @@ private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable ; } - private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) + private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void { $rootNode ->children() @@ -1138,7 +1240,7 @@ private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBe ; } - private function addPhpErrorsSection(ArrayNodeDefinition $rootNode) + private function addPhpErrorsSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -1169,7 +1271,7 @@ private function addPhpErrorsSection(ArrayNodeDefinition $rootNode) }) ->end() ->validate() - ->ifTrue(function ($v) { return !(\is_int($v) || \is_bool($v) || \is_array($v)); }) + ->ifTrue(fn ($v) => !(\is_int($v) || \is_bool($v) || \is_array($v))) ->thenInvalid('The "php_errors.log" parameter should be either an integer, a boolean, or an array') ->end() ->end() @@ -1184,7 +1286,7 @@ private function addPhpErrorsSection(ArrayNodeDefinition $rootNode) ; } - private function addExceptionsSection(ArrayNodeDefinition $rootNode) + private function addExceptionsSection(ArrayNodeDefinition $rootNode): void { $logLevels = (new \ReflectionClass(LogLevel::class))->getConstants(); @@ -1202,6 +1304,8 @@ private function addExceptionsSection(ArrayNodeDefinition $rootNode) return $v; } + trigger_deprecation('symfony/framework-bundle', '6.3', '"framework:exceptions" tag is deprecated. Unwrap it and replace your "framework:exception" tags\' "name" attribute by "class".'); + $v = $v['exception']; unset($v['exception']); @@ -1218,7 +1322,7 @@ private function addExceptionsSection(ArrayNodeDefinition $rootNode) ->scalarNode('log_level') ->info('The level of log message. Null to let Symfony decide.') ->validate() - ->ifTrue(function ($v) use ($logLevels) { return null !== $v && !\in_array($v, $logLevels, true); }) + ->ifTrue(fn ($v) => null !== $v && !\in_array($v, $logLevels, true)) ->thenInvalid(sprintf('The log level is not valid. Pick one among "%s".', implode('", "', $logLevels))) ->end() ->defaultNull() @@ -1226,11 +1330,11 @@ private function addExceptionsSection(ArrayNodeDefinition $rootNode) ->scalarNode('status_code') ->info('The status code of the response. Null or 0 to let Symfony decide.') ->beforeNormalization() - ->ifTrue(function ($v) { return 0 === $v; }) - ->then(function ($v) { return null; }) + ->ifTrue(fn ($v) => 0 === $v) + ->then(fn ($v) => null) ->end() ->validate() - ->ifTrue(function ($v) { return null !== $v && ($v < 100 || $v > 599); }) + ->ifTrue(fn ($v) => null !== $v && ($v < 100 || $v > 599)) ->thenInvalid('The status code is not valid. Pick a value between 100 and 599.') ->end() ->defaultNull() @@ -1242,7 +1346,7 @@ private function addExceptionsSection(ArrayNodeDefinition $rootNode) ; } - private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1250,14 +1354,14 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI ->info('Lock configuration') ->{$enableIfStandalone('symfony/lock', Lock::class)}() ->beforeNormalization() - ->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; }) + ->ifString()->then(fn ($v) => ['enabled' => true, 'resources' => $v]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['enabled']); }) - ->then(function ($v) { return $v + ['enabled' => true]; }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['enabled'])) + ->then(fn ($v) => $v + ['enabled' => true]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['resources']) && !isset($v['resource']); }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['resources']) && !isset($v['resource'])) ->then(function ($v) { $e = $v['enabled']; unset($v['enabled']); @@ -1267,7 +1371,7 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI ->end() ->addDefaultsIfNotSet() ->validate() - ->ifTrue(static function (array $config) { return $config['enabled'] && !$config['resources']; }) + ->ifTrue(static fn (array $config) => $config['enabled'] && !$config['resources']) ->thenInvalid('At least one resource must be defined.') ->end() ->fixXmlConfig('resource') @@ -1277,10 +1381,10 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI ->useAttributeAsKey('name') ->defaultValue(['default' => [class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphore' : 'flock']]) ->beforeNormalization() - ->ifString()->then(function ($v) { return ['default' => $v]; }) + ->ifString()->then(fn ($v) => ['default' => $v]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && array_is_list($v); }) + ->ifTrue(fn ($v) => \is_array($v) && array_is_list($v)) ->then(function ($v) { $resources = []; foreach ($v as $resource) { @@ -1295,7 +1399,7 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI ->end() ->prototype('array') ->performNoDeepMerging() - ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() ->prototype('scalar')->end() ->end() ->end() @@ -1305,7 +1409,7 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI ; } - private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1313,14 +1417,14 @@ private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $en ->info('Semaphore configuration') ->{$enableIfStandalone('symfony/semaphore', Semaphore::class)}() ->beforeNormalization() - ->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; }) + ->ifString()->then(fn ($v) => ['enabled' => true, 'resources' => $v]) ->end() ->beforeNormalization() - ->ifTrue 10000 (function ($v) { return \is_array($v) && !isset($v['enabled']); }) - ->then(function ($v) { return $v + ['enabled' => true]; }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['enabled'])) + ->then(fn ($v) => $v + ['enabled' => true]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['resources']) && !isset($v['resource']); }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['resources']) && !isset($v['resource'])) ->then(function ($v) { $e = $v['enabled']; unset($v['enabled']); @@ -1336,10 +1440,10 @@ private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $en ->useAttributeAsKey('name') ->requiresAtLeastOneElement() ->beforeNormalization() - ->ifString()->then(function ($v) { return ['default' => $v]; }) + ->ifString()->then(fn ($v) => ['default' => $v]) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && array_is_list($v); }) + ->ifTrue(fn ($v) => \is_array($v) && array_is_list($v)) ->then(function ($v) { $resources = []; foreach ($v as $resource) { @@ -1360,7 +1464,7 @@ private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $en ; } - private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1372,7 +1476,7 @@ private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enab ; } - private function addMessengerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addMessengerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1518,6 +1622,11 @@ function ($a) { ->thenInvalid('The "framework.messenger.reset_on_message" configuration option can be set to "true" only. To prevent services resetting after each message you can set the "--no-reset" option in "messenger:consume" command.') ->end() ->end() + ->arrayNode('stop_worker_on_signals') + ->defaultValue([]) + ->info('A list of signals that should stop the worker; defaults to SIGTERM and SIGINT.') + ->integerPrototype()->end() + ->end() ->scalarNode('default_bus')->defaultNull()->end() ->arrayNode('buses') ->defaultValue(['messenger.bus.default' => ['default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => []]]) @@ -1598,7 +1707,19 @@ function ($a) { ; } - private function addRobotsIndexSection(ArrayNodeDefinition $rootNode) + private function addSchedulerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('scheduler') + ->info('Scheduler configuration') + ->{$enableIfStandalone('symfony/scheduler', Schedule::class)}() + ->end() + ->end() + ; + } + + private function addRobotsIndexSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -1611,7 +1732,7 @@ private function addRobotsIndexSection(ArrayNodeDefinition $rootNode) ; } - private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1651,6 +1772,11 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->normalizeKeys(false) ->variablePrototype()->end() ->end() + ->arrayNode('vars') + ->info('Associative array: the default vars used to expand the templated URI.') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() ->integerNode('max_redirects') ->info('The maximum number of redirects to follow.') ->end() @@ -1723,6 +1849,11 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->variableNode('md5')->end() ->end() ->end() + ->arrayNode('extra') + ->info('Extra options for specific HTTP client') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() ->append($this->addHttpClientRetrySection()) ->end() ->end() @@ -1866,6 +1997,11 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->variableNode('md5')->end() ->end() ->end() + ->arrayNode('extra') + ->info('Extra options for specific HTTP client') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() ->append($this->addHttpClientRetrySection()) ->end() ->end() @@ -1927,9 +2063,7 @@ private function addHttpClientRetrySection() ->arrayNode('methods') ->beforeNormalization() ->ifArray() - ->then(function ($v) { - return array_map('strtoupper', $v); - }) + ->then(fn ($v) => array_map('strtoupper', $v)) ->end() ->prototype('scalar')->end() ->info('A list of HTTP methods that triggers a retry for this status code. When empty, all methods are retried') @@ -1947,7 +2081,7 @@ private function addHttpClientRetrySection() ; } - private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -1955,7 +2089,7 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl ->info('Mailer configuration') ->{$enableIfStandalone('symfony/mailer', Mailer::class)}() ->validate() - ->ifTrue(function ($v) { return isset($v['dsn']) && \count($v['transports']); }) + ->ifTrue(fn ($v) => isset($v['dsn']) && \count($v['transports'])) ->thenInvalid('"dsn" and "transports" cannot be used together.') ->end() ->fixXmlConfig('transport') @@ -1975,9 +2109,7 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl ->performNoDeepMerging() ->beforeNormalization() ->ifArray() - ->then(function ($v) { - return array_filter(array_values($v)); - }) + ->then(fn ($v) => array_filter(array_values($v))) ->end() ->prototype('scalar')->end() ->end() @@ -1989,8 +2121,8 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl ->prototype('array') ->normalizeKeys(false) ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v) || array_keys($v) !== ['value']; }) - ->then(function ($v) { return ['value' => $v]; }) + ->ifTrue(fn ($v) => !\is_array($v) || array_keys($v) !== ['value']) + ->then(fn ($v) => ['value' => $v]) ->end() ->children() ->variableNode('value')->end() @@ -2003,13 +2135,16 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl ; } - private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() ->arrayNode('notifier') ->info('Notifier configuration') ->{$enableIfStandalone('symfony/notifier', Notifier::class)}() + ->children() + ->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end() + ->end() ->fixXmlConfig('chatter_transport') ->children() ->arrayNode('chatter_transports') @@ -2052,7 +2187,48 @@ private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $ena ; } - private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addWebhookSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('webhook') + ->info('Webhook configuration') + ->{$enableIfStandalone('symfony/webhook', WebhookController::class)}() + ->children() + ->scalarNode('message_bus')->defaultValue('messenger.default_bus')->info('The message bus to use.')->end() + ->arrayNode('routing') + ->normalizeKeys(false) + ->useAttributeAsKey('type') + ->prototype('array') + ->children() + ->scalarNode('service') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->scalarNode('secret') + ->defaultValue('') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addRemoteEventSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + { + $rootNode + ->children() + ->arrayNode('remote-event') + ->info('RemoteEvent configuration') + ->{$enableIfStandalone('symfony/remote-event', RemoteEvent::class)}() + ->end() + ->end() + ; + } + + private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -2061,7 +2237,7 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $ ->{$enableIfStandalone('symfony/rate-limiter', TokenBucketLimiter::class)}() ->fixXmlConfig('limiter') ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && !isset($v['limiters']) && !isset($v['limiter']); }) + ->ifTrue(fn ($v) => \is_array($v) && !isset($v['limiters']) && !isset($v['limiter'])) ->then(function (array $v) { $newV = [ 'enabled' => $v['enabled'] ?? true, @@ -2097,7 +2273,6 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $ ->end() ->integerNode('limit') ->info('The maximum allowed hits in a fixed interval or burst') - ->isRequired() ->end() ->scalarNode('interval') ->info('Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).') @@ -2112,6 +2287,10 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $ ->end() ->end() ->end() + ->validate() + ->ifTrue(fn ($v) => 'no_limit' !== $v['policy'] && !isset($v['limit'])) + ->thenInvalid('A limit must be provided when using a policy different than "no_limit".') + ->end() ->end() ->end() ->end() @@ -2120,7 +2299,7 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $ ; } - private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() @@ -2153,7 +2332,7 @@ private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIf ; } - private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void { $rootNode ->children() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 7bf72ee6c801f..7acee5c278e07 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -12,8 +12,8 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; use Composer\InstalledVersions; -use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\Reader; +use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\Types\ContextFactory; @@ -31,6 +31,8 @@ use Symfony\Bundle\FullStack; use Symfony\Bundle\MercureBundle\MercureBundle; use Symfony\Component\Asset\PackageInterface; +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; use Symfony\Component\BrowserKit\AbstractBrowser; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -45,6 +47,7 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\ResourceCheckerInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; @@ -81,8 +84,10 @@ use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; use Symfony\Component\HttpClient\RetryableHttpClient; use Symfony\Component\HttpClient\ScopingHttpClient; +use Symfony\Component\HttpClient\UriTemplateHttpClient; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\AsController; +use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver; @@ -95,26 +100,13 @@ use Symfony\Component\Lock\LockInterface; use Symfony\Component\Lock\PersistingStoreInterface; use Symfony\Component\Lock\Store\StoreFactory; -use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; -use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; -use Symfony\Component\Mailer\Bridge\Infobip\Transport\InfobipTransportFactory as InfobipMailerTransportFactory; -use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; -use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; -use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; -use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory; -use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory; -use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; -use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; -use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory; +use Symfony\Component\Mailer\Bridge as MailerBridge; use Symfony\Component\Mailer\Command\MailerTestCommand; use Symfony\Component\Mailer\EventListener\MessengerTransportListener; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mercure\HubRegistry; use Symfony\Component\Messenger\Attribute\AsMessageHandler; -use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; -use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; -use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; -use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory; +use Symfony\Component\Messenger\Bridge as MessengerBridge; use Symfony\Component\Messenger\Command\StatsCommand; use Symfony\Component\Messenger\Handler\BatchHandlerInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; @@ -122,63 +114,12 @@ use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Middleware\RouterContextMiddleware; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; -use Symfony\Component\Messenger\Transport\TransportFactoryInterface; +use Symfony\Component\Messenger\Transport\TransportFactoryInterface as MessengerTransportFactoryInterface; use Symfony\Component\Messenger\Transport\TransportInterface; use Symfony\Component\Mime\Header\Headers; use Symfony\Component\Mime\MimeTypeGuesserInterface; use Symfony\Component\Mime\MimeTypes; -use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; -use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; -use Symfony\Component\Notifier\Bridge\Chatwork\ChatworkTransportFactory; -use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; -use Symfony\Component\Notifier\Bridge\ContactEveryone\ContactEveryoneTransportFactory; -use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; -use Symfony\Component\Notifier\Bridge\Engagespot\EngagespotTransportFactory; -use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; -use Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory; -use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; -use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; -use Symfony\Component\Notifier\Bridge\FortySixElks\FortySixElksTransportFactory; -use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; -use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; -use Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; -use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; -use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; -use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; -use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; -use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; -use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory as MailjetNotifierTransportFactory; -use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; -use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; -use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransport; -use Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; -use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory; -use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; -use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; -use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; -use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; -use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; -use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; -use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory as SendinblueNotifierTransportFactory; -use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; -use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; -use Symfony\Component\Notifier\Bridge\Sms77\Sms77TransportFactory; -use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; -use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; -use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; -use Symfony\Component\Notifier\Bridge\SmsFactor\SmsFactorTransportFactory; -use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; -use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; -use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; -use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransport; -use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; -use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; -use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; -use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; -use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; +use Symfony\Component\Notifier\Bridge as NotifierBridge; use Symfony\Component\Notifier\ChatterInterface; use Symfony\Component\Notifier\Notifier; use Symfony\Component\Notifier\Recipient\Recipient; @@ -198,7 +139,11 @@ use Symfony\Component\RateLimiter\LimiterInterface; use Symfony\Component\RateLimiter\RateLimiterFactory; use Symfony\Component\RateLimiter\Storage\CacheStorage; +use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer; +use Symfony\Component\RemoteEvent\RemoteEvent; use Symfony\Component\Routing\Loader\Psr4DirectoryLoader; +use Symfony\Component\Scheduler\Attribute\AsSchedule; +use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory; use Symfony\Component\Security\Core\AuthenticationEvents; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; @@ -213,13 +158,14 @@ use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\String\LazyString; use Symfony\Component\String\Slugger\SluggerInterface; -use Symfony\Component\Translation\Bridge\Crowdin\CrowdinProviderFactory; -use Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory; -use Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory; +use Symfony\Component\Translation\Bridge as TranslationBridge; use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand; use Symfony\Component\Translation\Extractor\PhpAstExtractor; use Symfony\Component\Translation\LocaleSwitcher; @@ -232,6 +178,7 @@ use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\ObjectInitializerInterface; use Symfony\Component\Validator\Validation; +use Symfony\Component\Webhook\Controller\WebhookController; use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow; use Symfony\Component\Workflow\WorkflowInterface; @@ -257,6 +204,8 @@ class FrameworkExtension extends Extension /** * Responds to the app.config configuration parameter. * + * @return void + * * @throws LogicException */ public function load(array $configs, ContainerBuilder $container) @@ -384,12 +333,20 @@ public function load(array $configs, ContainerBuilder $container) $this->registerAssetsConfiguration($config['assets'], $container, $loader); } + if ($this->readConfigEnabled('asset_mapper', $container, $config['asset_mapper'])) { + if (!class_exists(AssetMapper::class)) { + throw new LogicException('AssetMapper support cannot be enabled as the AssetMapper component is not installed. Try running "composer require symfony/asset-mapper".'); + } + + $this->registerAssetMapperConfiguration($config['asset_mapper'], $container, $loader, $this->readConfigEnabled('assets', $container, $config['assets'])); + } + if ($this->readConfigEnabled('http_client', $container, $config['http_client'])) { $this->registerHttpClientConfiguration($config['http_client'], $container, $loader); } if ($this->readConfigEnabled('mailer', $container, $config['mailer'])) { - $this->registerMailerConfiguration($config['mailer'], $container, $loader); + $this->registerMailerConfiguration($config['mailer'], $container, $loader, $this->readConfigEnabled('webhook', $container, $config['webhook'])); if (!$this->hasConsole() || !class_exists(MailerTestCommand::class)) { $container->removeDefinition('console.command.mailer_test'); @@ -412,11 +369,21 @@ public function load(array $configs, ContainerBuilder $container) $container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']); if ($this->readConfigEnabled('serializer', $container, $config['serializer'])) { - if (!class_exists(\Symfony\Component\Serializer\Serializer::class)) { + if (!class_exists(Serializer::class)) { throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".'); } $this->registerSerializerConfiguration($config['serializer'], $container, $loader); + } else { + $container->register('.argument_resolver.request_payload.no_serializer', Serializer::class) + ->addError('You can neither use "#[MapRequestPayload]" nor "#[MapQueryString]" since the Serializer component is not ' + .(class_exists(Serializer::class) ? 'enabled. Try setting "framework.serializer" to true.' : 'installed. Try running "composer require symfony/serializer-pack".') + ); + + $container->getDefinition('argument_resolver.request_payload') + ->replaceArgument(0, new Reference('.argument_resolver.request_payload.no_serializer', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE)); + + $container->removeDefinition('console.command.serializer_debug'); } if ($propertyInfoEnabled) { @@ -507,9 +474,20 @@ public function load(array $configs, ContainerBuilder $container) // validation depends on form, annotations being registered $this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled); + $messengerEnabled = $this->readConfigEnabled('messenger', $container, $config['messenger']); + + if ($this->readConfigEnabled('scheduler', $container, $config['scheduler'])) { + if (!$messengerEnabled) { + throw new LogicException('Scheduler support cannot be enabled as the Messenger component is not '.(interface_exists(MessageBusInterface::class) ? 'enabled.' : 'installed. Try running "composer require symfony/messenger".')); + } + $this->registerSchedulerConfiguration($config['scheduler'], $container, $loader); + } else { + $container->removeDefinition('console.command.scheduler_debug'); + } + // messenger depends on validation being registered - if ($this->readConfigEnabled('messenger', $container, $config['messenger'])) { - $this->registerMessengerConfiguration($config['messenger'], $container, $loader, $config['validation']); + if ($messengerEnabled) { + $this->registerMessengerConfiguration($config['messenger'], $container, $loader, $this->readConfigEnabled('validation', $container, $config['validation'])); } else { $container->removeDefinition('console.command.messenger_consume_messages'); $container->removeDefinition('console.command.messenger_stats'); @@ -521,7 +499,7 @@ public function load(array $configs, ContainerBuilder $container) $container->removeDefinition('console.command.messenger_failed_messages_remove'); $container->removeDefinition('cache.messenger.restart_workers_signal'); - if ($container->hasDefinition('messenger.transport.amqp.factory') && !class_exists(AmqpTransportFactory::class)) { + if ($container->hasDefinition('messenger.transport.amqp.factory') && !class_exists(MessengerBridge\Amqp\Transport\AmqpTransportFactory::class)) { if (class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)) { $container->getDefinition('messenger.transport.amqp.factory') ->setClass(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class) @@ -531,7 +509,7 @@ public function load(array $configs, ContainerBuilder $container) } } - if ($container->hasDefinition('messenger.transport.redis.factory') && !class_exists(RedisTransportFactory::class)) { + if ($container->hasDefinition('messenger.transport.redis.factory') && !class_exists(MessengerBridge\Redis\Transport\RedisTransportFactory::class)) { if (class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)) { $container->getDefinition('messenger.transport.redis.factory') ->setClass(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class) @@ -544,12 +522,20 @@ public function load(array $configs, ContainerBuilder $container) // notifier depends on messenger, mailer being registered if ($this->readConfigEnabled('notifier', $container, $config['notifier'])) { - $this->registerNotifierConfiguration($config['notifier'], $container, $loader); + $this->registerNotifierConfiguration($config['notifier'], $container, $loader, $this->readConfigEnabled('webhook', $container, $config['webhook'])); } // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier, serializer being registered $this->registerProfilerConfiguration($config['profiler'], $container, $loader); + if ($this->readConfigEnabled('webhook', $container, $config['webhook'])) { + $this->registerWebhookConfiguration($config['webhook'], $container, $loader); + } + + if ($this->readConfigEnabled('remote-event', $container, $config['remote-event'])) { + $this->registerRemoteEventConfiguration($config['remote-event'], $container, $loader); + } + if ($this->readConfigEnabled('html_sanitizer', $container, $config['html_sanitizer'])) { if (!class_exists(HtmlSanitizerConfig::class)) { throw new LogicException('HtmlSanitizer support cannot be enabled as the HtmlSanitizer component is not installed. Try running "composer require symfony/html-sanitizer".'); @@ -644,7 +630,7 @@ public function load(array $configs, ContainerBuilder $container) ->addTag('messenger.message_handler'); $container->registerForAutoconfiguration(BatchHandlerInterface::class) ->addTag('messenger.message_handler'); - $container->registerForAutoconfiguration(TransportFactoryInterface::class) + $container->registerForAutoconfiguration(MessengerTransportFactoryInterface::class) ->addTag('messenger.transport_factory'); $container->registerForAutoconfiguration(MimeTypeGuesserInterface::class) ->addTag('mime.mime_type_guesser'); @@ -664,7 +650,9 @@ public function load(array $configs, ContainerBuilder $container) $container->registerAttributeForAutoconfiguration(AsController::class, static function (ChildDefinition $definition, AsController $attribute): void { $definition->addTag('controller.service_arguments'); }); - + $container->registerAttributeForAutoconfiguration(AsRemoteEventConsumer::class, static function (ChildDefinition $definition, AsRemoteEventConsumer $attribute): void { + $definition->addTag('remote_event.consumer', ['consumer' => $attribute->name]); + }); $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute, \ReflectionClass|\ReflectionMethod $reflector): void { $tagAttributes = get_object_vars($attribute); $tagAttributes['from_transport'] = $tagAttributes['fromTransport']; @@ -677,6 +665,12 @@ public function load(array $configs, ContainerBuilder $container) } $definition->addTag('messenger.message_handler', $tagAttributes); }); + $container->registerAttributeForAutoconfiguration(AsTargetedValueResolver::class, static function (ChildDefinition $definition, AsTargetedValueResolver $attribute): void { + $definition->addTag('controller.targeted_value_resolver', $attribute->name ? ['name' => $attribute->name] : []); + }); + $container->registerAttributeForAutoconfiguration(AsSchedule::class, static function (ChildDefinition $definition, AsSchedule $attribute): void { + $definition->addTag('scheduler.schedule_provider', ['name' => $attribute->name]); + }); if (!$container->getParameter('kernel.debug')) { // remove tagged iterator argument for resource checkers @@ -712,7 +706,7 @@ protected function hasConsole(): bool return class_exists(Application::class); } - private function registerFormConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerFormConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('form.php'); @@ -743,7 +737,7 @@ private function registerFormConfiguration(array $config, ContainerBuilder $cont } } - private function registerHttpCacheConfiguration(array $config, ContainerBuilder $container, bool $httpMethodOverride) + private function registerHttpCacheConfiguration(array $config, ContainerBuilder $container, bool $httpMethodOverride): void { $options = $config; unset($options['enabled']); @@ -752,6 +746,10 @@ private function registerHttpCacheConfiguration(array $config, ContainerBuilder unset($options['private_headers']); } + if (!$options['skip_response_headers']) { + unset($options['skip_response_headers']); + } + $container->getDefinition('http_cache') ->setPublic($config['enabled']) ->replaceArgument(3, $options); @@ -764,7 +762,7 @@ private function registerHttpCacheConfiguration(array $config, ContainerBuilder } } - private function registerEsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerEsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if (!$this->readConfigEnabled('esi', $container, $config)) { $container->removeDefinition('fragment.renderer.esi'); @@ -775,7 +773,7 @@ private function registerEsiConfiguration(array $config, ContainerBuilder $conta $loader->load('esi.php'); } - private function registerSsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if (!$this->readConfigEnabled('ssi', $container, $config)) { $container->removeDefinition('fragment.renderer.ssi'); @@ -786,7 +784,7 @@ private function registerSsiConfiguration(array $config, ContainerBuilder $conta $loader->load('ssi.php'); } - private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if (!$this->readConfigEnabled('fragments', $container, $config)) { $container->removeDefinition('fragment.renderer.hinclude'); @@ -800,7 +798,7 @@ private function registerFragmentsConfiguration(array $config, ContainerBuilder $container->setParameter('fragment.path', $config['path']); } - private function registerProfilerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerProfilerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if (!$this->readConfigEnabled('profiler', $container, $config)) { // this is needed for the WebProfiler to work even if the profiler is disabled @@ -866,7 +864,7 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $ ->addArgument($config['collect_parameter']); } - private function registerWorkflowConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerWorkflowConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if (!$config['enabled']) { $container->removeDefinition('console.command.workflow_dump'); @@ -998,6 +996,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $container->setDefinition($workflowId, $workflowDefinition); $container->setDefinition(sprintf('%s.definition', $workflowId), $definitionDefinition); $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name.'.'.$type); + $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name); // Validate Workflow if ('state_machine' === $workflow['type']) { @@ -1006,9 +1005,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $validator = new Workflow\Validator\WorkflowValidator(); } - $trs = array_map(function (Reference $ref) use ($container): Workflow\Transition { - return $container->get((string) $ref); - }, $transitions); + $trs = array_map(fn (Reference $ref): Workflow\Transition => $container->get((string) $ref), $transitions); $realDefinition = new Workflow\Definition($places, $trs, $initialMarking); $validator->validate($realDefinition, $name); @@ -1065,7 +1062,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ } } - private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('debug_prod.php'); @@ -1078,7 +1075,7 @@ private function registerDebugConfiguration(array $config, ContainerBuilder $con $debug = $container->getParameter('kernel.debug'); - if ($debug) { + if ($debug && !$container->hasParameter('debug.container.dump')) { $container->setParameter('debug.container.dump', '%kernel.build_dir%/%kernel.container_class%.xml'); } @@ -1086,12 +1083,12 @@ private function registerDebugConfiguration(array $config, ContainerBuilder $con $loader->load('debug.php'); } - $definition = $container->findDefinition('debug.debug_handlers_listener'); + $definition = $container->findDefinition('debug.error_handler_configurator'); if (false === $config['log']) { - $definition->replaceArgument(1, null); + $definition->replaceArgument(0, null); } elseif (true !== $config['log']) { - $definition->replaceArgument(2, $config['log']); + $definition->replaceArgument(1, $config['log']); } if (!$config['throw']) { @@ -1106,7 +1103,7 @@ private function registerDebugConfiguration(array $config, ContainerBuilder $con } } - private function registerRouterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $enabledLocales = []) + private function registerRouterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $enabledLocales = []): void { if (!$this->readConfigEnabled('router', $container, $config)) { $container->removeDefinition('console.command.router_debug'); @@ -1157,7 +1154,7 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co } } - private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('session.php'); @@ -1202,7 +1199,7 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c $container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']); } - private function registerRequestConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerRequestConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if ($config['formats']) { $loader->load('request.php'); @@ -1212,7 +1209,7 @@ private function registerRequestConfiguration(array $config, ContainerBuilder $c } } - private function registerAssetsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerAssetsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('assets.php'); @@ -1245,6 +1242,57 @@ private function registerAssetsConfiguration(array $config, ContainerBuilder $co } } + private function registerAssetMapperConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $assetEnabled): void + { + $loader->load('asset_mapper.php'); + + if (!$assetEnabled) { + $container->removeDefinition('asset_mapper.asset_package'); + } + + $publicDirName = $this->getPublicDirectoryName($container); + $container->getDefinition('asset_mapper') + ->setArgument(3, $config['public_prefix']) + ->setArgument(4, $publicDirName) + ->setArgument(5, $config['extensions']) + ; + + $paths = $config['paths']; + foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) { + if ($container->fileExists($dir = $bundle['path'].'/Resources/public') || $container->fileExists($dir = $bundle['path'].'/public')) { + $paths[$dir] = sprintf('bundles/%s', preg_replace('/bundle$/', '', strtolower($name))); + } + } + $container->getDefinition('asset_mapper.repository') + ->setArgument(0, $paths); + + $container->getDefinition('asset_mapper.command.compile') + ->setArgument(4, $publicDirName); + + if (!$config['server']) { + $container->removeDefinition('asset_mapper.dev_server_subscriber'); + } + + $container->getDefinition('asset_mapper.compiler.css_asset_url_compiler') + ->setArgument(0, $config['strict_mode']); + + $container->getDefinition('asset_mapper.compiler.javascript_import_path_compiler') + ->setArgument(0, $config['strict_mode']); + + $container + ->getDefinition('asset_mapper.importmap.manager') + ->replaceArgument(1, $config['importmap_path']) + ->replaceArgument(2, $config['vendor_dir']) + ->replaceArgument(3, $config['provider']) + ; + + $container + ->getDefinition('asset_mapper.importmap.renderer') + ->replaceArgument(2, $config['importmap_polyfill'] ?? ImportMapManager::POLYFILL_URL) + ->replaceArgument(3, $config['importmap_script_attributes']) + ; + } + /** * Returns a definition for an asset package. */ @@ -1290,7 +1338,7 @@ private function createVersion(ContainerBuilder $container, ?string $version, ?s return new Reference('assets.empty_version_strategy'); } - private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales) + private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales): void { if (!$this->readConfigEnabled('translator', $container, $config)) { $container->removeDefinition('console.command.translation_debug'); @@ -1390,9 +1438,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $finder = Finder::create() ->followLinks() ->files() - ->filter(function (\SplFileInfo $file) { - return 2 <= substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename()); - }) + ->filter(fn (\SplFileInfo $file) => 2 <= substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename())) ->in($dir) ->sortByName() ; @@ -1415,9 +1461,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder 'resource_files' => $files, 'scanned_directories' => $scannedDirectories = array_merge($dirs, $nonExistingDirs), 'cache_vary' => [ - 'scanned_directories' => array_map(static function (string $dir) use ($projectDir): string { - return str_starts_with($dir, $projectDir.'/') ? substr($dir, 1 + \strlen($projectDir)) : $dir; - }, $scannedDirectories), + 'scanned_directories' => array_map(static fn (string $dir): string => str_starts_with($dir, $projectDir.'/') ? substr($dir, 1 + \strlen($projectDir)) : $dir, $scannedDirectories), ], ] ); @@ -1439,9 +1483,9 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder } $classToServices = [ - CrowdinProviderFactory::class => 'translation.provider_factory.crowdin', - LocoProviderFactory::class => 'translation.provider_factory.loco', - LokaliseProviderFactory::class => 'translation.provider_factory.lokalise', + TranslationBridge\Crowdin\CrowdinProviderFactory::class => 'translation.provider_factory.crowdin', + TranslationBridge\Loco\LocoProviderFactory::class => 'translation.provider_factory.loco', + TranslationBridge\Lokalise\LokaliseProviderFactory::class => 'translation.provider_factory.lokalise', ]; $parentPackages = ['symfony/framework-bundle', 'symfony/translation', 'symfony/http-client']; @@ -1485,7 +1529,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $container->getDefinition('translation.provider_collection')->setArgument(0, $config['providers']); } - private function registerValidationConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $propertyInfoEnabled) + private function registerValidationConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $propertyInfoEnabled): void { if (!$this->readConfigEnabled('validation', $container, $config)) { $container->removeDefinition('console.command.validator_debug'); @@ -1558,7 +1602,7 @@ private function registerValidationConfiguration(array $config, ContainerBuilder } } - private function registerValidatorMapping(ContainerBuilder $container, array $config, array &$files) + private function registerValidatorMapping(ContainerBuilder $container, array $config, array &$files): void { $fileRecorder = function ($extension, $path) use (&$files) { $files['yaml' === $extension ? 'yml' : $extension][] = $path; @@ -1573,8 +1617,8 @@ private function registerValidatorMapping(ContainerBuilder $container, array $co $configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config'; if ( - $container->fileExists($file = $configDir.'/validation.yaml', false) || - $container->fileExists($file = $configDir.'/validation.yml', false) + $container->fileExists($file = $configDir.'/validation.yaml', false) + || $container->fileExists($file = $configDir.'/validation.yml', false) ) { $fileRecorder('yml', $file); } @@ -1596,14 +1640,14 @@ private function registerValidatorMapping(ContainerBuilder $container, array $co $this->registerMappingFilesFromConfig($container, $config, $fileRecorder); } - private function registerMappingFilesFromDir(string $dir, callable $fileRecorder) + private function registerMappingFilesFromDir(string $dir, callable $fileRecorder): void { foreach (Finder::create()->followLinks()->files()->in($dir)->name('/\.(xml|ya?ml)$/')->sortByName() as $file) { $fileRecorder($file->getExtension(), $file->getRealPath()); } } - private function registerMappingFilesFromConfig(ContainerBuilder $container, array $config, callable $fileRecorder) + private function registerMappingFilesFromConfig(ContainerBuilder $container, array $config, callable $fileRecorder): void { foreach ($config['mapping']['paths'] as $path) { if (is_dir($path)) { @@ -1620,7 +1664,7 @@ private function registerMappingFilesFromConfig(ContainerBuilder $container, arr } } - private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader) + private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader): void { if (!$this->isInitializedConfigEnabled('annotations')) { return; @@ -1632,18 +1676,6 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde $loader->load('annotations.php'); - // registerUniqueLoader exists since doctrine/annotations v1.6 - if (!method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { - // registerLoader exists only in doctrine/annotations v1 - if (method_exists(AnnotationRegistry::class, 'registerLoader')) { - $container->getDefinition('annotations.dummy_registry') - ->setMethodCalls([['registerLoader', ['class_exists']]]); - } else { - // remove the dummy registry when doctrine/annotations v2 is used - $container->removeDefinition('annotations.dummy_registry'); - } - } - if ('none' === $config['cache']) { $container->removeDefinition('annotations.cached_reader'); @@ -1682,7 +1714,7 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde $container->removeDefinition('annotations.psr_cached_reader'); } - private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if (!$this->readConfigEnabled('property_access', $container, $config)) { return; @@ -1708,7 +1740,7 @@ private function registerPropertyAccessConfiguration(array $config, ContainerBui ; } - private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if (!$this->readConfigEnabled('secrets', $container, $config)) { $container->removeDefinition('console.command.secrets_set'); @@ -1748,7 +1780,7 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c } } - private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { if (!$this->readConfigEnabled('csrf_protection', $container, $config)) { return; @@ -1770,7 +1802,7 @@ private function registerSecurityCsrfConfiguration(array $config, ContainerBuild } } - private function registerSerializerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSerializerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('serializer.php'); @@ -1793,6 +1825,12 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->removeDefinition('serializer.normalizer.mime_message'); } + // compat with Symfony < 6.3 + if (!is_subclass_of(ProblemNormalizer::class, SerializerAwareInterface::class)) { + $container->getDefinition('serializer.normalizer.problem') + ->setArguments(['%kernel.debug%']); + } + $serializerLoaders = []; if (isset($config['enable_annotations']) && $config['enable_annotations']) { if ($container->getParameter('kernel.debug')) { @@ -1822,8 +1860,8 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder } if ( - $container->fileExists($file = $configDir.'/serialization.yaml', false) || - $container->fileExists($file = $configDir.'/serialization.yml', false) + $container->fileExists($file = $configDir.'/serialization.yaml', false) + || $container->fileExists($file = $configDir.'/serialization.yml', false) ) { $fileRecorder('yml', $file); } @@ -1865,7 +1903,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder } } - private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader) + private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void { if (!interface_exists(PropertyInfoExtractorInterface::class)) { throw new LogicException('PropertyInfo support cannot be enabled as the PropertyInfo component is not installed. Try running "composer require symfony/property-info".'); @@ -1892,7 +1930,7 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container, } } - private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('lock.php'); @@ -1906,8 +1944,10 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont foreach ($resourceStores as $resourceStore) { $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs); $storeDefinition = new Definition(PersistingStoreInterface::class); - $storeDefinition->setFactory([StoreFactory::class, 'createStore']); - $storeDefinition->setArguments([$resourceStore]); + $storeDefinition + ->setFactory([StoreFactory::class, 'createStore']) + ->setArguments([$resourceStore]) + ->addTag('lock.store'); $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition); @@ -1938,7 +1978,7 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont } } - private function registerSemaphoreConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerSemaphoreConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('semaphore.php'); @@ -1971,7 +2011,20 @@ private function registerSemaphoreConfiguration(array $config, ContainerBuilder } } - private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $validationConfig) + private function registerSchedulerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!class_exists(SchedulerTransportFactory::class)) { + throw new LogicException('Scheduler support cannot be enabled as the Scheduler component is not installed. Try running "composer require symfony/scheduler".'); + } + + $loader->load('scheduler.php'); + + if (!$this->hasConsole()) { + $container->removeDefinition('console.command.scheduler_debug'); + } + } + + private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $validationEnabled): void { if (!interface_exists(MessageBusInterface::class)) { throw new LogicException('Messenger support cannot be enabled as the Messenger component is not installed. Try running "composer require symfony/messenger".'); @@ -1987,22 +2040,26 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $container->removeDefinition('serializer.normalizer.flatten_exception'); } - if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { + if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', MessengerBridge\Amqp\Transport\AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.amqp.factory')->addTag('messenger.transport_factory'); } - if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { + if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', MessengerBridge\Redis\Transport\RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.redis.factory')->addTag('messenger.transport_factory'); } - if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { + if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', MessengerBridge\AmazonSqs\Transport\AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.sqs.factory')->addTag('messenger.transport_factory'); } - if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { + if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', MessengerBridge\Beanstalkd\Transport\BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.beanstalkd.factory')->addTag('messenger.transport_factory'); } + if ($config['stop_worker_on_signals']) { + $container->getDefinition('messenger.listener.stop_worker_signals_listener')->replaceArgument(0, $config['stop_worker_on_signals']); + } + if (null === $config['default_bus'] && 1 === \count($config['buses'])) { $config['default_bus'] = key($config['buses']); } @@ -2033,7 +2090,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder } foreach ($middleware as $middlewareItem) { - if (!$validationConfig['enabled'] && \in_array($middlewareItem['id'], ['validation', 'messenger.middleware.validation'], true)) { + if (!$validationEnabled && \in_array($middlewareItem['id'], ['validation', 'messenger.middleware.validation'], true)) { throw new LogicException('The Validation middleware is only available when the Validator compo 10000 nent is installed and enabled. Try running "composer require symfony/validator".'); } } @@ -2146,13 +2203,15 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder } } - $failureTransportReferencesByTransportName = array_map(function ($failureTransportName) use ($senderReferences) { - return $senderReferences[$failureTransportName]; - }, $failureTransportsByName); + $failureTransportReferencesByTransportName = array_map(fn ($failureTransportName) => $senderReferences[$failureTransportName], $failureTransportsByName); $messageToSendersMapping = []; foreach ($config['routing'] as $message => $messageConfiguration) { - if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) { + if ('*' !== $message && !class_exists($message) && !interface_exists($message, false) && !preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++\*$/', $message)) { + if (str_contains($message, '*')) { + throw new LogicException(sprintf('Invalid Messenger routing configuration: invalid namespace "%s" wildcard.', $message)); + } + throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message)); } @@ -2210,7 +2269,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder } } - private function registerCacheConfiguration(array $config, ContainerBuilder $container) + private function registerCacheConfiguration(array $config, ContainerBuilder $container): void { if (!class_exists(DefaultMarshaller::class)) { $container->removeDefinition('cache.default_marshaller'); @@ -2327,31 +2386,48 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con } } - private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('http_client.php'); $options = $config['default_options'] ?? []; $retryOptions = $options['retry_failed'] ?? ['enabled' => false]; unset($options['retry_failed']); - $container->getDefinition('http_client')->setArguments([$options, $config['max_host_connections'] ?? 6]); + $defaultUriTemplateVars = $options['vars'] ?? []; + unset($options['vars']); + $container->getDefinition('http_client.transport')->setArguments([$options, $config['max_host_connections'] ?? 6]); if (!$hasPsr18 = ContainerBuilder::willBeAvailable('psr/http-client', ClientInterface::class, ['symfony/framework-bundle', 'symfony/http-client'])) { $container->removeDefinition('psr18.http_client'); $container->removeAlias(ClientInterface::class); } - if (!ContainerBuilder::willBeAvailable('php-http/httplug', HttpClient::class, ['symfony/framework-bundle', 'symfony/http-client'])) { - $container->removeDefinition(HttpClient::class); + if (!$hasHttplug = ContainerBuilder::willBeAvailable('php-http/httplug', HttpAsyncClient::class, ['symfony/framework-bundle', 'symfony/http-client'])) { + $container->removeDefinition('httplug.http_client'); + $container->removeAlias(HttpAsyncClient::class); + $container->removeAlias(HttpClient::class); } if ($this->readConfigEnabled('http_client.retry_failed', $container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, 'http_client', $container); } - $httpClientId = ($retryOptions['enabled'] ?? false) ? 'http_client.retryable.inner' : ($this->isInitializedConfigEnabled('profiler') ? '.debug.http_client.inner' : 'http_client'); + if ($hasUriTemplate = class_exists(UriTemplateHttpClient::class)) { + if (ContainerBuilder::willBeAvailable('guzzlehttp/uri-template', \GuzzleHttp\UriTemplate\UriTemplate::class, [])) { + $container->setAlias('http_client.uri_template_expander', 'http_client.uri_template_expander.guzzle'); + } elseif (ContainerBuilder::willBeAvailable('rize/uri-template', \Rize\UriTemplate::class, [])) { + $container->setAlias('http_client.uri_template_expander', 'http_client.uri_template_expander.rize'); + } + + $container + ->getDefinition('http_client.uri_template') + ->setArgument(2, $defaultUriTemplateVars); + } elseif ($defaultUriTemplateVars) { + throw new LogicException('Support for URI template requires symfony/http-client 6.3 or higher, try upgrading.'); + } + foreach ($config['scoped_clients'] as $name => $scopeConfig) { - if ('http_client' === $name) { + if ($container->has($name)) { throw new InvalidArgumentException(sprintf('Invalid scope name: "%s" is reserved.', $name)); } @@ -2366,20 +2442,31 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $container->register($name, ScopingHttpClient::class) ->setFactory([ScopingHttpClient::class, 'forBaseUri']) - ->setArguments([new Reference($httpClientId), $baseUri, $scopeConfig]) + ->setArguments([new Reference('http_client.transport'), $baseUri, $scopeConfig]) ->addTag('http_client.client') ; } else { $container->register($name, ScopingHttpClient::class) - ->setArguments([new Reference($httpClientId), [$scope => $scopeConfig], $scope]) + ->setArguments([new Reference('http_client.transport'), [$scope => $scopeConfig], $scope]) ->addTag('http_client.client') ; } - if ($this->readConfigEnabled('http_client.scoped_clients.'.$name.'retry_failed', $container, $retryOptions)) { + if ($this->readConfigEnabled('http_client.scoped_clients.'.$name.'.retry_failed', $container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, $name, $container); } + if ($hasUriTemplate) { + $container + ->register($name.'.uri_template', UriTemplateHttpClient::class) + ->setDecoratedService($name, null, 7) // Between TraceableHttpClient (5) and RetryableHttpClient (10) + ->setArguments([ + new Reference($name.'.uri_template.inner'), + new Reference('http_client.uri_template_expander', ContainerInterface::NULL_ON_INVALID_REFERENCE), + $defaultUriTemplateVars, + ]); + } + $container->registerAliasForArgument($name, HttpClientInterface::class); if ($hasPsr18) { @@ -2388,21 +2475,24 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $container->registerAliasForArgument('psr18.'.$name, ClientInterface::class, $name); } + + if ($hasHttplug) { + $container->setDefinition('httplug.'.$name, new ChildDefinition('httplug.http_client')) + ->replaceArgument(0, new Reference($name)); + + $container->registerAliasForArgument('httplug.'.$name, HttpAsyncClient::class, $name); + } } if ($responseFactoryId = $config['mock_response_factory'] ?? null) { - $container->register($httpClientId.'.mock_client', MockHttpClient::class) - ->setDecoratedService($httpClientId, null, -10) // lower priority than TraceableHttpClient + $container->register('http_client.mock_client', MockHttpClient::class) + ->setDecoratedService('http_client.transport', null, -10) // lower priority than TraceableHttpClient (5) ->setArguments([new Reference($responseFactoryId)]); } } - private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container) + private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container): void { - if (!class_exists(RetryableHttpClient::class)) { - throw new LogicException('Support for retrying failed requests requires symfony/http-client 5.2 or higher, try upgrading.'); - } - if (null !== $options['retry_strategy']) { $retryStrategy = new Reference($options['retry_strategy']); } else { @@ -2429,12 +2519,12 @@ private function registerRetryableHttpClient(array $options, string $name, Conta $container ->register($name.'.retryable', RetryableHttpClient::class) - ->setDecoratedService($name, null, 10) // higher priority than TraceableHttpClient + ->setDecoratedService($name, null, 10) // higher priority than TraceableHttpClient (5) ->setArguments([new Reference($name.'.retryable.inner'), $retryStrategy, $options['max_retries'], new Reference('logger')]) ->addTag('monolog.logger', ['channel' => 'http_client']); } - private function registerMailerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerMailerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $webhookEnabled): void { if (!class_exists(Mailer::class)) { throw new LogicException('Mailer support cannot be enabled as the component is not installed. Try running "composer require symfony/mailer".'); @@ -2457,17 +2547,18 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co } $classToServices = [ - GmailTransportFactory::class => 'mailer.transport_factory.gmail', - InfobipMailerTransportFactory::class => 'mailer.transport_factory.infobip', - MailgunTransportFactory::class => 'mailer.transport_factory.mailgun', - MailjetTransportFactory::class => 'mailer.transport_factory.mailjet', - MailPaceTransportFactory::class => 'mailer.transport_factory.mailpace', - MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', - OhMySmtpTransportFactory::class => 'mailer.transport_factory.ohmysmtp', - PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', - SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid', - SendinblueTransportFactory::class => 'mailer.transport_factory.sendinblue', - SesTransportFactory::class => 'mailer.transport_factory.amazon', + MailerBridge\Google\Transport\GmailTransportFactory::class => 'mailer.transport_factory.gmail', + MailerBridge\Infobip\Transport\InfobipTransportFactory::class => 'mailer.transport_factory.infobip', + MailerBridge\MailerSend\Transport\MailerSendTransportFactory::class => 'mailer.transport_factory.mailersend', + MailerBridge\Mailgun\Transport\MailgunTransportFactory::class => 'mailer.transport_factory.mailgun', + MailerBridge\Mailjet\Transport\MailjetTransportFactory::class => 'mailer.transport_factory.mailjet', + MailerBridge\MailPace\Transport\MailPaceTransportFactory::class => 'mailer.transport_factory.mailpace', + MailerBridge\Mailchimp\Transport\MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', + MailerBridge\OhMySmtp\Transport\OhMySmtpTransportFactory::class => 'mailer.transport_factory.ohmysmtp', + MailerBridge\Postmark\Transport\PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', + MailerBridge\Sendgrid\Transport\SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid', + MailerBridge\Sendinblue\Transport\SendinblueTransportFactory::class => 'mailer.transport_factory.sendinblue', + MailerBridge\Amazon\Transport\SesTransportFactory::class => 'mailer.transport_factory.amazon', ]; foreach ($classToServices as $class => $service) { @@ -2478,6 +2569,21 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co } } + if ($webhookEnabled) { + $webhookRequestParsers = [ + MailerBridge\Mailgun\Webhook\MailgunRequestParser::class => 'mailer.webhook.request_parser.mailgun', + MailerBridge\Postmark\Webhook\PostmarkRequestParser::class => 'mailer.webhook.request_parser.postmark', + ]; + + foreach ($webhookRequestParsers as $class => $service) { + $package = substr($service, \strlen('mailer.transport_factory.')); + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'])) { + $container->removeDefinition($service); + } + } + } + $envelopeListener = $container->getDefinition('mailer.envelope_listener'); $envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null); $envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null); @@ -2500,9 +2606,13 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co if (!class_exists(MessengerTransportListener::class)) { $container->removeDefinition('mailer.messenger_transport_listener'); } + + if ($webhookEnabled) { + $loader->load('mailer_webhook.php'); + } } - private function registerNotifierConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerNotifierConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $webhookEnabled): void { if (!class_exists(Notifier::class)) { throw new LogicException('Notifier support cannot be enabled as the component is not installed. Try running "composer require symfony/notifier".'); @@ -2531,6 +2641,18 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ $container->removeDefinition('notifier.channel.email'); } + foreach (['texter', 'chatter', 'notifier.channel.chat', 'notifier.channel.email', 'notifier.channel.sms'] as $serviceId) { + if (!$container->hasDefinition($serviceId)) { + continue; + } + + if (false === $messageBus = $config['message_bus']) { + $container->getDefinition($serviceId)->replaceArgument(1, null); + } else { + $container->getDefinition($serviceId)->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE)); + } + } + if ($this->isInitializedConfigEnabled('messenger')) { if ($config['notification_on_failed_messages']) { $container->getDefinition('notifier.failed_message_listener')->addTag('kernel.event_subscriber'); @@ -2554,58 +2676,69 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ ->addTag('texter.transport_factory'); $classToServices = [ - AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms', - AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', - ChatworkTransportFactory::class => 'notifier.transport_factory.chatwork', - ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', - ContactEveryoneTransportFactory::class => 'notifier.transport_factory.contact-everyone', - DiscordTransportFactory::class => 'notifier.transport_factory.discord', - EngagespotTransportFactory::class => 'notifier.transport_factory.engagespot', - EsendexTransportFactory::class => 'notifier.transport_factory.esendex', - ExpoTransportFactory::class => 'notifier.transport_factory.expo', - FakeChatTransportFactory::class => 'notifier.transport_factory.fake-chat', - FakeSmsTransportFactory::class => 'notifier.transport_factory.fake-sms', - FirebaseTransportFactory::class => 'notifier.transport_factory.firebase', - FortySixElksTransportFactory::class => 'notifier.transport_factory.forty-six-elks', - FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile', - GatewayApiTransportFactory::class => 'notifier.transport_factory.gateway-api', - GitterTransportFactory::class => 'notifier.transport_factory.gitter', - GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat', - InfobipTransportFactory::class => 'notifier.transport_factory.infobip', - IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', - KazInfoTehTransportFactory::class => 'notifier.transport_factory.kaz-info-teh', - LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', - LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', - MailjetNotifierTransportFactory::class => 'notifier.transport_factory.mailjet', - MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', - MercureTransportFactory::class => 'notifier.transport_factory.mercure', - MessageBirdTransport::class => 'notifier.transport_factory.message-bird', - MessageMediaTransportFactory::class => 'notifier.transport_factory.message-media', - MicrosoftTeamsTransportFactory::class => 'notifier.transport_factory.microsoft-teams', - MobytTransportFactory::class => 'notifier.transport_factory.mobyt', - OctopushTransportFactory::class => 'notifier.transport_factory.octopush', - OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', - OrangeSmsTransportFactory::class => 'notifier.transport_factory.orange-sms', - OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', - RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', - SendberryTransportFactory::class => 'notifier.transport_factory.sendberry', - SendinblueNotifierTransportFactory::class => 'notifier.transport_factory.sendinblue', - SinchTransportFactory::class => 'notifier.transport_factory.sinch', - SlackTransportFactory::class => 'notifier.transport_factory.slack', - Sms77TransportFactory::class => 'notifier.transport_factory.sms77', - SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi', - SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras', - SmscTransportFactory::class => 'notifier.transport_factory.smsc', - SmsFactorTransportFactory::class => 'notifier.transport_factory.sms-factor', - SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit', - TelegramTransportFactory::class => 'notifier.transport_factory.telegram', - TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', - TurboSmsTransport::class => 'notifier.transport_factory.turbo-sms', - TwilioTransportFactory::class => 'notifier.transport_factory.twilio', - VonageTransportFactory::class => 'notifier.transport_factory.vonage', - YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', - ZendeskTransportFactory::class => 'notifier.transport_factory.zendesk', - ZulipTransportFactory::class => 'notifier.transport_factory.zulip', + NotifierBridge\AllMySms\AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms', + NotifierBridge\AmazonSns\AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', + NotifierBridge\Bandwidth\BandwidthTransportFactory::class => 'notifier.transport_factory.bandwidth', + NotifierBridge\Chatwork\ChatworkTransportFactory::class => 'notifier.transport_factory.chatwork', + NotifierBridge\Clickatell\ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', + NotifierBridge\ContactEveryone\ContactEveryoneTransportFactory::class => 'notifier.transport_factory.contact-everyone', + NotifierBridge\Discord\DiscordTransportFactory::class => 'notifier.transport_factory.discord', + NotifierBridge\Engagespot\EngagespotTransportFactory::class => 'notifier.transport_factory.engagespot', + NotifierBridge\Esendex\EsendexTransportFactory::class => 'notifier.transport_factory.esendex', + NotifierBridge\Expo\ExpoTransportFactory::class => 'notifier.transport_factory.expo', + NotifierBridge\FakeChat\FakeChatTransportFactory::class => 'notifier.transport_factory.fake-chat', + NotifierBridge\FakeSms\FakeSmsTransportFactory::class => 'notifier.transport_factory.fake-sms', + NotifierBridge\Firebase\FirebaseTransportFactory::class => 'notifier.transport_factory.firebase', + NotifierBridge\FortySixElks\FortySixElksTransportFactory::class => 'notifier.transport_factory.forty-six-elks', + NotifierBridge\FreeMobile\FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile', + NotifierBridge\GatewayApi\GatewayApiTransportFactory::class => 'notifier.transport_factory.gateway-api', + NotifierBridge\Gitter\GitterTransportFactory::class => 'notifier.transport_factory.gitter', + NotifierBridge\GoogleChat\GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat', + NotifierBridge\Infobip\InfobipTransportFactory::class => 'notifier.transport_factory.infobip', + NotifierBridge\Iqsms\IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', + NotifierBridge\Isendpro\IsendproTransportFactory::class => 'notifier.transport_factory.isendpro', + NotifierBridge\KazInfoTeh\KazInfoTehTransportFactory::class => 'notifier.transport_factory.kaz-info-teh', + NotifierBridge\LightSms\LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', + NotifierBridge\LineNotify\LineNotifyTransportFactory::class => 'notifier.transport_factory.line-notify', + NotifierBridge\LinkedIn\LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', + NotifierBridge\Mailjet\MailjetTransportFactory::class => 'notifier.transport_factory.mailjet', + NotifierBridge\Mastodon\MastodonTransportFactory::class => 'notifier.transport_factory.mastodon', + NotifierBridge\Mattermost\MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', + NotifierBridge\Mercure\MercureTransportFactory::class => 'notifier.transport_factory.mercure', + NotifierBridge\MessageBird\MessageBirdTransport::class => 'notifier.transport_factory.message-bird', + NotifierBridge\MessageMedia\MessageMediaTransportFactory::class => 'notifier.transport_factory.message-media', + NotifierBridge\MicrosoftTeams\MicrosoftTeamsTransportFactory::class => 'notifier.transport_factory.microsoft-teams', + NotifierBridge\Mobyt\MobytTransportFactory::class => 'notifier.transport_factory.mobyt', + NotifierBridge\Octopush\OctopushTransportFactory::class => 'notifier.transport_factory.octopush', + NotifierBridge\OneSignal\OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', + NotifierBridge\OrangeSms\OrangeSmsTransportFactory::class => 'notifier.transport_factory.orange-sms', + NotifierBridge\OvhCloud\OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', + NotifierBridge\PagerDuty\PagerDutyTransportFactory::class => 'notifier.transport_factory.pager-duty', + NotifierBridge\Plivo\PlivoTransportFactory::class => 'notifier.transport_factory.plivo', + NotifierBridge\Pushover\PushoverTransportFactory::class => 'notifier.transport_factory.pushover', + NotifierBridge\RingCentral\RingCentralTransportFactory::class => 'notifier.transport_factory.ring-central', + NotifierBridge\RocketChat\RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', + NotifierBridge\Sendberry\SendberryTransportFactory::class => 'notifier.transport_factory.sendberry', + NotifierBridge\SimpleTextin\SimpleTextinTransportFactory::class => 'notifier.transport_factory.simple-textin', + NotifierBridge\Sendinblue\SendinblueTransportFactory::class => 'notifier.transport_factory.sendinblue', + NotifierBridge\Sinch\SinchTransportFactory::class => 'notifier.transport_factory.sinch', + NotifierBridge\Slack\SlackTransportFactory::class => 'notifier.transport_factory.slack', + NotifierBridge\Sms77\Sms77TransportFactory::class => 'notifier.transport_factory.sms77', + NotifierBridge\Smsapi\SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi', + NotifierBridge\SmsBiuras\SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras', + NotifierBridge\Smsc\SmscTransportFactory::class => 'notifier.transport_factory.smsc', + NotifierBridge\SmsFactor\SmsFactorTransportFactory::class => 'notifier.transport_factory.sms-factor', + NotifierBridge\SpotHit\SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit', + NotifierBridge\Telegram\TelegramTransportFactory::class => 'notifier.transport_factory.telegram', + NotifierBridge\Telnyx\TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', + NotifierBridge\Termii\TermiiTransportFactory::class => 'notifier.transport_factory.termii', + NotifierBridge\TurboSms\TurboSmsTransport::class => 'notifier.transport_factory.turbo-sms', + NotifierBridge\Twilio\TwilioTransportFactory::class => 'notifier.transport_factory.twilio', + NotifierBridge\Twitter\TwitterTransportFactory::class => 'notifier.transport_factory.twitter', + NotifierBridge\Vonage\VonageTransportFactory::class => 'notifier.transport_factory.vonage', + NotifierBridge\Yunpian\YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', + NotifierBridge\Zendesk\ZendeskTransportFactory::class => 'notifier.transport_factory.zendesk', + NotifierBridge\Zulip\ZulipTransportFactory::class => 'notifier.transport_factory.zulip', ]; $parentPackages = ['symfony/framework-bundle', 'symfony/notifier']; @@ -2618,21 +2751,21 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ } } - if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages) && \in_array(MercureBundle::class, $container->getParameter('kernel.bundles'), true)) { - $container->getDefinition($classToServices[MercureTransportFactory::class]) + if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', NotifierBridge\Mercure\MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages) && \in_array(MercureBundle::class, $container->getParameter('kernel.bundles'), true)) { + $container->getDefinition($classToServices[NotifierBridge\Mercure\MercureTransportFactory::class]) ->replaceArgument('$registry', new Reference(HubRegistry::class)); - } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages)) { - $container->removeDefinition($classToServices[MercureTransportFactory::class]); + } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', NotifierBridge\Mercure\MercureTransportFactory::class, $parentPackages)) { + $container->removeDefinition($classToServices[NotifierBridge\Mercure\MercureTransportFactory::class]); } - if (ContainerBuilder::willBeAvailable('symfony/fake-chat-notifier', FakeChatTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'])) { - $container->getDefinition($classToServices[FakeChatTransportFactory::class]) + if (ContainerBuilder::willBeAvailable('symfony/fake-chat-notifier', NotifierBridge\FakeChat\FakeChatTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'])) { + $container->getDefinition($classToServices[NotifierBridge\FakeChat\FakeChatTransportFactory::class]) ->replaceArgument('$mailer', new Reference('mailer')) ->replaceArgument('$logger', new Reference('logger')); } - if (ContainerBuilder::willBeAvailable('symfony/fake-sms-notifier', FakeSmsTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'])) { - $container->getDefinition($classToServices[FakeSmsTransportFactory::class]) + if (ContainerBuilder::willBeAvailable('symfony/fake-sms-notifier', NotifierBridge\FakeSms\FakeSmsTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'])) { + $container->getDefinition($classToServices[NotifierBridge\FakeSms\FakeSmsTransportFactory::class]) ->replaceArgument('$mailer', new Reference('mailer')) ->replaceArgument('$logger', new Reference('logger')); } @@ -2645,9 +2778,43 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ $notifier->addMethodCall('addAdminRecipient', [new Reference($id)]); } } + + if ($webhookEnabled) { + $loader->load('notifier_webhook.php'); + } + } + + private function registerWebhookConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + { + if (!class_exists(WebhookController::class)) { + throw new LogicException('Webhook support cannot be enabled as the component is not installed. Try running "composer require symfony/webhook".'); + } + + $loader->load('webhook.php'); + + $parsers = []; + foreach ($config['routing'] as $type => $cfg) { + $parsers[$type] = [ + 'parser' => new Reference($cfg['service']), + 'secret' => $cfg['secret'], + ]; + } + + $controller = $container->getDefinition('webhook.controller'); + $controller->replaceArgument(0, $parsers); + $controller->replaceArgument(1, new Reference($config['message_bus'])); } - private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerRemoteEventConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + { + if (!class_exists(RemoteEvent::class)) { + throw new LogicException('RemoteEvent support cannot be enabled as the component is not installed. Try running "composer require symfony/remote-event".'); + } + + $loader->load('remote_event.php'); + } + + private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('rate_limiter.php'); @@ -2686,6 +2853,8 @@ private function registerRateLimiterConfiguration(array $config, ContainerBuilde /** * @deprecated since Symfony 6.2 + * + * @return void */ public static function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig) { @@ -2721,7 +2890,7 @@ public static function registerRateLimiter(ContainerBuilder $container, string $ $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter'); } - private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('uid.php'); @@ -2746,7 +2915,7 @@ private function registerUidConfiguration(array $config, ContainerBuilder $conta } } - private function registerHtmlSanitizerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + private function registerHtmlSanitizerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('html_sanitizer.php'); @@ -2887,4 +3056,20 @@ private function writeConfigEnabled(string $path, bool $value, array &$config): $this->configsEnabled[$path] = $value; $config['enabled'] = $value; } + + private function getPublicDirectoryName(ContainerBuilder $container): string + { + $defaultPublicDir = 'public'; + + $composerFilePath = $container->getParameter('kernel.project_dir').'/composer.json'; + + if (!file_exists($composerFilePath)) { + return $defaultPublicDir; + } + + $container->addResource(new FileResource($composerFilePath)); + $composerConfig = json_decode(file_get_contents($composerFilePath), true); + + return $composerConfig['extra']['public-dir'] ?? $defaultPublicDir; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php b/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php index 53cae12ebbcff..d7bdc8e6684f9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php +++ b/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php @@ -32,9 +32,6 @@ final class SuggestMissingPackageSubscriber implements EventSubscriberInterface 'mongodb' => ['DoctrineMongoDBBundle', 'doctrine/mongodb-odm-bundle'], '_default' => ['Doctrine ORM', 'symfony/orm-pack'], ], - 'generate' => [ - '_default' => ['SensioGeneratorBundle', 'sensio/generator-bundle'], - ], 'make' => [ '_default' => ['MakerBundle', 'symfony/maker-bundle --dev'], ], diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index b51e19a5a2341..ffb96a23e5f5b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -60,6 +60,7 @@ use Symfony\Component\Mime\DependencyInjection\AddMimeTypeGuesserPass; use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass; use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; +use Symfony\Component\Scheduler\DependencyInjection\AddScheduleMessengerPass; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass; use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass; @@ -89,9 +90,13 @@ class_exists(Registry::class); */ class FrameworkBundle extends Bundle { + /** + * @return void + */ public function boot() { - ErrorHandler::register(null, false)->throwAt($this->container->getParameter('debug.error_handler.throw_at'), true); + $handler = ErrorHandler::register(null, false); + $this->container->get('debug.error_handler_configurator')->configure($handler); if ($this->container->getParameter('kernel.http_method_override')) { Request::enableHttpMethodParameterOverride(); @@ -102,6 +107,9 @@ public function boot() } } + /** + * @return void + */ public function build(ContainerBuilder $container) { parent::build($container); @@ -158,6 +166,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32); $container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING); $this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class); + $this->addCompilerPassIfExists($container, AddScheduleMessengerPass::class); $this->addCompilerPassIfExists($container, MessengerPass::class); $this->addCompilerPassIfExists($container, HttpClientPass::class); $this->addCompilerPassIfExists($container, AddAutoMappingConfigurationPass::class); @@ -174,7 +183,7 @@ public function build(ContainerBuilder $container) } } - private function addCompilerPassIfExists(ContainerBuilder $container, string $class, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) + private function addCompilerPassIfExists(ContainerBuilder $container, string $class, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): void { $container->addResource(new ClassExistenceResource($class)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php index fa04ff332433d..3ab28a1f81db9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php @@ -128,6 +128,9 @@ public function registerBundles(): iterable } } + /** + * @return void + */ public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(function (ContainerBuilder $container) use ($loader) { @@ -168,12 +171,10 @@ public function registerContainerConfiguration(LoaderInterface $loader) /* @var ContainerPhpFileLoader $kernelLoader */ $kernelLoader = $loader->getResolver()->resolve($file); $kernelLoader->setCurrentDir(\dirname($file)); - $instanceof = &\Closure::bind(function &() { return $this->instanceof; }, $kernelLoader, $kernelLoader)(); + $instanceof = &\Closure::bind(fn &() => $this->instanceof, $kernelLoader, $kernelLoader)(); $valuePreProcessor = AbstractConfigurator::$valuePreProcessor; - AbstractConfigurator::$valuePreProcessor = function ($value) { - return $this === $value ? new Reference('kernel') : $value; - }; + AbstractConfigurator::$valuePreProcessor = fn ($value) => $this === $value ? new Reference('kernel') : $value; try { $configureContainer->getClosure($this)(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader, $container); diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index 0f4a3843fb843..0379be8f5bc97 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -73,6 +73,8 @@ public function getProfile(): HttpProfile|false|null * Enables the profiler for the very next request. * * If the profiler is not enabled, the call to this method does nothing. + * + * @return void */ public function enableProfiler() { @@ -86,6 +88,8 @@ public function enableProfiler() * * By default, the Client reboots the Kernel for each request. This method * allows to keep the same kernel across requests. + * + * @return void */ public function disableReboot() { @@ -94,6 +98,8 @@ public function disableReboot() /** * Enables kernel reboot between requests. + * + * @return void */ public function enableReboot() { @@ -108,7 +114,7 @@ public function enableReboot() public function loginUser(object $user, string $firewallContext = 'main'): static { if (!interface_exists(UserInterface::class)) { - throw new \LogicException(sprintf('"%s" requires symfony/security-core to be installed.', __METHOD__)); + throw new \LogicException(sprintf('"%s" requires symfony/security-core to be installed. Try running "composer require symfony/security-core".', __METHOD__)); } if (!$user instanceof UserInterface) { @@ -132,9 +138,7 @@ public function loginUser(object $user, string $firewallContext = 'main'): stati $session->set('_security_'.$firewallContext, serialize($token)); $session->save(); - $domains = array_unique(array_map(function (Cookie $cookie) use ($session) { - return $cookie->getName() === $session->getName() ? $cookie->getDomain() : ''; - }, $this->getCookieJar()->all())) ?: ['']; + $domains = array_unique(array_map(fn (Cookie $cookie) => $cookie->getName() === $session->getName() ? $cookie->getDomain() : '', $this->getCookieJar()->all())) ?: ['']; foreach ($domains as $domain) { $cookie = new Cookie($session->getName(), $session->getId(), null, null, $domain); $this->getCookieJar()->set($cookie); diff --git a/src/Symfony/Bundle/FrameworkBundle/README.md b/src/Symfony/Bundle/FrameworkBundle/README.md index 1b7dc2c3cf312..402d8718d4875 100644 --- a/src/Symfony/Bundle/FrameworkBundle/README.md +++ b/src/Symfony/Bundle/FrameworkBundle/README.md @@ -7,7 +7,7 @@ Symfony full-stack framework. Sponsor ------- -The FrameworkBundle for Symfony 6.2 is [backed][1] by [alximy][2]. +The FrameworkBundle for Symfony 6.3 is [backed][1] by [alximy][2]. A team of passionate humans from very different backgrounds, sharing their love of PHP, Symfony and its ecosystem. Their CTO, Expert developers, tech leads, can help diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php index ec86fed495498..f043c87ec7d73 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php @@ -12,7 +12,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Doctrine\Common\Annotations\AnnotationReader; -use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\PsrCachedReader; use Doctrine\Common\Annotations\Reader; use Symfony\Bundle\FrameworkBundle\CacheWarmer\AnnotationsCacheWarmer; @@ -23,13 +22,7 @@ return static function (ContainerConfigurator $container) { $container->services() ->set('annotations.reader', AnnotationReader::class) - ->call('addGlobalIgnoredName', [ - 'required', - service('annotations.dummy_registry')->nullOnInvalid(), // dummy arg to register class_exists as annotation loader only when required - ]) - - ->set('annotations.dummy_registry', AnnotationRegistry::class) - ->call('registerUniqueLoader', ['class_exists']) + ->call('addGlobalIgnoredName', ['required']) // @deprecated since Symfony 6.3 ->set('annotations.cached_reader', PsrCachedReader::class) ->args([ diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php new file mode 100644 index 0000000000000..5d471ea623258 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\AssetMapper\AssetMapper; +use Symfony\Component\AssetMapper\AssetMapperCompiler; +use Symfony\Component\AssetMapper\AssetMapperDevServerSubscriber; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\AssetMapperRepository; +use Symfony\Component\AssetMapper\Command\AssetMapperCompileCommand; +use Symfony\Component\AssetMapper\Command\ImportMapExportCommand; +use Symfony\Component\AssetMapper\Command\ImportMapRemoveCommand; +use Symfony\Component\AssetMapper\Command\ImportMapRequireCommand; +use Symfony\Component\AssetMapper\Command\ImportMapUpdateCommand; +use Symfony\Component\AssetMapper\Compiler\CssAssetUrlCompiler; +use Symfony\Component\AssetMapper\Compiler\JavaScriptImportPathCompiler; +use Symfony\Component\AssetMapper\Compiler\SourceMappingUrlsCompiler; +use Symfony\Component\AssetMapper\ImportMap\ImportMapManager; +use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; +use Symfony\Component\AssetMapper\MapperAwareAssetPackage; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('asset_mapper', AssetMapper::class) + ->args([ + service('asset_mapper.repository'), + service('asset_mapper_compiler'), + param('kernel.project_dir'), + abstract_arg('asset public prefix'), + abstract_arg('public directory name'), + abstract_arg('extensions map'), + ]) + ->alias(AssetMapperInterface::class, 'asset_mapper') + ->set('asset_mapper.repository', AssetMapperRepository::class) + ->args([ + abstract_arg('array of asset mapper paths'), + param('kernel.project_dir'), + ]) + ->set('asset_mapper.asset_package', MapperAwareAssetPackage::class) + ->decorate('assets._default_package') + ->args([ + service('.inner'), + service('asset_mapper'), + ]) + + ->set('asset_mapper.dev_server_subscriber', AssetMapperDevServerSubscriber::class) + ->args([ + service('asset_mapper'), + ]) + ->tag('kernel.event_subscriber', ['event' => RequestEvent::class]) + + ->set('asset_mapper.command.compile', AssetMapperCompileCommand::class) + ->args([ + service('asset_mapper'), + service('asset_mapper.importmap.manager'), + service('filesystem'), + param('kernel.project_dir'), + abstract_arg('public directory name'), + param('kernel.debug'), + ]) + ->tag('console.command') + + ->set('asset_mapper_compiler', AssetMapperCompiler::class) + ->args([ + tagged_iterator('asset_mapper.compiler'), + ]) + + ->set('asset_mapper.compiler.css_asset_url_compiler', CssAssetUrlCompiler::class) + ->args([ + abstract_arg('strict mode'), + ]) + ->tag('asset_mapper.compiler') + + ->set('asset_mapper.compiler.source_mapping_urls_compiler', SourceMappingUrlsCompiler::class) + ->tag('asset_mapper.compiler') + + ->set('asset_mapper.compiler.javascript_import_path_compiler', JavaScriptImportPathCompiler::class) + ->args([ + abstract_arg('strict mode'), + ]) + ->tag('asset_mapper.compiler') + + ->set('asset_mapper.importmap.manager', ImportMapManager::class) + ->args([ + service('asset_mapper'), + abstract_arg('importmap.php path'), + abstract_arg('vendor directory'), + abstract_arg('provider'), + ]) + ->alias(ImportMapManager::class, 'asset_mapper.importmap.manager') + + ->set('asset_mapper.importmap.renderer', ImportMapRenderer::class) + ->args([ + service('asset_mapper.importmap.manager'), + param('kernel.charset'), + abstract_arg('polyfill URL'), + abstract_arg('script HTML attributes'), + ]) + + ->set('asset_mapper.importmap.command.require', ImportMapRequireCommand::class) + ->args([ + service('asset_mapper.importmap.manager'), + service('asset_mapper'), + ]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.remove', ImportMapRemoveCommand::class) + ->args([service('asset_mapper.importmap.manager')]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.update', ImportMapUpdateCommand::class) + ->args([service('asset_mapper.importmap.manager')]) + ->tag('console.command') + + ->set('asset_mapper.importmap.command.export', ImportMapExportCommand::class) + ->args([service('asset_mapper.importmap.manager')]) + ->tag('console.command') + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php index 679d74c80dc37..2ea62a0b71882 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php @@ -71,6 +71,11 @@ ->private() ->tag('cache.pool') + ->set('cache.scheduler') + ->parent('cache.app') + ->private() + ->tag('cache.pool') + ->set('cache.adapter.system', AdapterInterface::class) ->abstract() ->factory([AbstractAdapter::class, 'createSystemCache']) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.php index abf9ded5e5949..df218013a2315 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.php @@ -47,7 +47,7 @@ ->set('data_collector.events', EventDataCollector::class) ->args([ - service('debug.event_dispatcher')->ignoreOnInvalid(), + tagged_iterator('event_dispatcher.dispatcher', 'name'), service('request_stack')->ignoreOnInvalid(), ]) ->tag('data_collector', ['template' => '@WebProfiler/Collector/events.html.twig', 'id' => 'events', 'priority' => 290]) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php index d64cd058e61f8..bfe7e3bc10b40 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php @@ -42,13 +42,15 @@ use Symfony\Component\Console\EventListener\ErrorListener; use Symfony\Component\Dotenv\Command\DebugCommand as DotenvDebugCommand; use Symfony\Component\Messenger\Command\ConsumeMessagesCommand; -use Symfony\Component\Messenger\Command\DebugCommand; +use Symfony\Component\Messenger\Command\DebugCommand as MessengerDebugCommand; use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand; use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand; use Symfony\Component\Messenger\Command\FailedMessagesShowCommand; use Symfony\Component\Messenger\Command\SetupTransportsCommand; use Symfony\Component\Messenger\Command\StatsCommand; use Symfony\Component\Messenger\Command\StopWorkersCommand; +use Symfony\Component\Scheduler\Command\DebugCommand as SchedulerDebugCommand; +use Symfony\Component\Serializer\Command\DebugCommand as SerializerDebugCommand; use Symfony\Component\Translation\Command\TranslationPullCommand; use Symfony\Component\Translation\Command\TranslationPushCommand; use Symfony\Component\Translation\Command\XliffLintCommand; @@ -172,7 +174,7 @@ ]) ->tag('console.command') - ->set('console.command.messenger_debug', DebugCommand::class) + ->set('console.command.messenger_debug', MessengerDebugCommand::class) ->args([ [], // Message to handlers mapping ]) @@ -218,6 +220,12 @@ ]) ->tag('console.command') + ->set('console.command.scheduler_debug', SchedulerDebugCommand::class) + ->args([ + tagged_locator('scheduler.schedule_provider', 'name'), + ]) + ->tag('console.command') + ->set('console.command.router_debug', RouterDebugCommand::class) ->args([ service('router'), @@ -232,6 +240,12 @@ ]) ->tag('console.command') + ->set('console.command.serializer_debug', SerializerDebugCommand::class) + ->args([ + service('serializer.mapping.class_metadata_factory'), + ]) + ->tag('console.command') + ->set('console.command.translation_debug', TranslationDebugCommand::class) ->args([ service('translator'), diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.php index f381b018f0629..7f71e2ee93094 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\HttpKernel\Debug\ErrorHandlerConfigurator; use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener; @@ -18,9 +19,9 @@ $container->parameters()->set('debug.error_handler.throw_at', -1); $container->services() - ->set('debug.debug_handlers_listener', DebugHandlersListener::class) + ->set('debug.error_handler_configurator', ErrorHandlerConfigurator::class) + ->public() ->args([ - null, // Exception handler service('monolog.logger.php')->nullOnInvalid(), null, // Log levels map for enabled error levels param('debug.error_handler.throw_at'), @@ -28,9 +29,11 @@ param('kernel.debug'), service('monolog.logger.deprecation')->nullOnInvalid(), ]) - ->tag('kernel.event_subscriber') ->tag('monolog.logger', ['channel' => 'php']) + ->set('debug.debug_handlers_listener', DebugHandlersListener::class) + ->tag('kernel.event_subscriber') + ->set('debug.file_link_formatter', FileLinkFormatter::class) ->args([param('debug.file_link_format')]) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/http_client.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/http_client.php index ba70b90ad654b..23b794f45b2dd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/http_client.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/http_client.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Http\Client\HttpAsyncClient; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; @@ -18,11 +19,12 @@ use Symfony\Component\HttpClient\HttplugClient; use Symfony\Component\HttpClient\Psr18Client; use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Component\HttpClient\UriTemplateHttpClient; use Symfony\Contracts\HttpClient\HttpClientInterface; return static function (ContainerConfigurator $container) { $container->services() - ->set('http_client', HttpClientInterface::class) + ->set('http_client.transport', HttpClientInterface::class) ->factory([HttpClient::class, 'create']) ->args([ [], // default options @@ -31,6 +33,10 @@ ->call('setLogger', [service('logger')->ignoreOnInvalid()]) ->tag('monolog.logger', ['channel' => 'http_client']) ->tag('kernel.reset', ['method' => 'reset', 'on_invalid' => 'ignore']) + + ->set('http_client', HttpClientInterface::class) + ->factory('current') + ->args([[service('http_client.transport')]]) ->tag('http_client.client') ->alias(HttpClientInterface::class, 'http_client') @@ -44,13 +50,17 @@ ->alias(ClientInterface::class, 'psr18.http_client') - ->set(\Http\Client\HttpClient::class, HttplugClient::class) + ->set('httplug.http_client', HttplugClient::class) ->args([ service('http_client'), service(ResponseFactoryInterface::class)->ignoreOnInvalid(), service(StreamFactoryInterface::class)->ignoreOnInvalid(), ]) + ->alias(HttpAsyncClient::class, 'httplug.http_client') + ->alias(\Http\Client\HttpClient::class, 'httplug.http_client') + ->deprecate('symfony/framework-bundle', '6.3', 'The "%alias_id%" service is deprecated, use "'.ClientInterface::class.'" instead.') + ->set('http_client.abstract_retry_strategy', GenericRetryStrategy::class) ->abstract() ->args([ @@ -60,5 +70,25 @@ abstract_arg('max delay ms'), abstract_arg('jitter'), ]) + + ->set('http_client.uri_template', UriTemplateHttpClient::class) + ->decorate('http_client', null, 7) // Between TraceableHttpClient (5) and RetryableHttpClient (10) + ->args([ + service('.inner'), + service('http_client.uri_template_expander')->nullOnInvalid(), + abstract_arg('default vars'), + ]) + + ->set('http_client.uri_template_expander.guzzle', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [\GuzzleHttp\UriTemplate\UriTemplate::class, 'expand'], + ]) + + ->set('http_client.uri_template_expander.rize', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [inline_service(\Rize\UriTemplate::class), 'expand'], + ]) ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php index e20594093c9dd..d352eb5bee856 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php @@ -15,6 +15,7 @@ use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; use Symfony\Component\Mailer\Bridge\Infobip\Transport\InfobipTransportFactory; use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; +use Symfony\Component\Mailer\Bridge\MailerSend\Transport\MailerSendTransportFactory; use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory; @@ -51,6 +52,10 @@ ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.mailersend', MailerSendTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.mailchimp', MandrillTransportFactory::class) ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php new file mode 100644 index 0000000000000..7780b3df51e78 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_webhook.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mailer\Bridge\Mailgun\RemoteEvent\MailgunPayloadConverter; +use Symfony\Component\Mailer\Bridge\Mailgun\Webhook\MailgunRequestParser; +use Symfony\Component\Mailer\Bridge\Postmark\RemoteEvent\PostmarkPayloadConverter; +use Symfony\Component\Mailer\Bridge\Postmark\Webhook\PostmarkRequestParser; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mailer.payload_converter.mailgun', MailgunPayloadConverter::class) + ->set('mailer.webhook.request_parser.mailgun', MailgunRequestParser::class) + ->args([service('mailer.payload_converter.mailgun')]) + ->alias(MailgunRequestParser::class, 'mailer.webhook.request_parser.mailgun') + + ->set('mailer.payload_converter.postmark', PostmarkPayloadConverter::class) + ->set('mailer.webhook.request_parser.postmark', PostmarkRequestParser::class) + ->args([service('mailer.payload_converter.postmark')]) + ->alias(PostmarkRequestParser::class, 'mailer.webhook.request_parser.postmark') + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php index d0d9900d2e506..0096ff1e099f3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php @@ -23,7 +23,8 @@ use Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener; use Symfony\Component\Messenger\EventListener\StopWorkerOnCustomStopExceptionListener; use Symfony\Component\Messenger\EventListener\StopWorkerOnRestartSignalListener; -use Symfony\Component\Messenger\EventListener\StopWorkerOnSigtermSignalListener; +use Symfony\Component\Messenger\EventListener\StopWorkerOnSignalsListener; +use Symfony\Component\Messenger\Handler\RedispatchMessageHandler; use Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware; use Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware; use Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware; @@ -35,7 +36,7 @@ use Symfony\Component\Messenger\Middleware\ValidationMiddleware; use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy; use Symfony\Component\Messenger\RoutableMessageBus; -use Symfony\Component\Messenger\Transport\InMemoryTransportFactory; +use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory; use Symfony\Component\Messenger\Transport\Sender\SendersLocator; use Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer; use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; @@ -75,6 +76,7 @@ ->tag('serializer.normalizer', ['priority' => -880]) ->set('messenger.transport.native_php_serializer', PhpSerializer::class) + ->alias('messenger.default_serializer', 'messenger.transport.native_php_serializer') // Middleware ->set('messenger.middleware.handle_message', HandleMessageMiddleware::class) @@ -199,8 +201,9 @@ ->tag('kernel.event_subscriber') ->tag('monolog.logger', ['channel' => 'messenger']) - ->set('messenger.listener.stop_worker_on_sigterm_signal_listener', StopWorkerOnSigtermSignalListener::class) + ->set('messenger.listener.stop_worker_signals_listener', StopWorkerOnSignalsListener::class) ->args([ + null, service('logger')->ignoreOnInvalid(), ]) ->tag('kernel.event_subscriber') @@ -219,5 +222,11 @@ abstract_arg('message bus locator'), service('messenger.default_bus'), ]) + + ->set('messenger.redispatch_message_handler', RedispatchMessageHandler::class) + ->args([ + service('messenger.default_bus'), + ]) + 10000 ->tag('messenger.message_handler') ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php index fde0533140809..6ce674148a878 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php @@ -52,15 +52,24 @@ ->tag('notifier.channel', ['channel' => 'browser']) ->set('notifier.channel.chat', ChatChannel::class) - ->args([service('chatter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('chatter.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'chat']) ->set('notifier.channel.sms', SmsChannel::class) - ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('texter.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'sms']) ->set('notifier.channel.email', EmailChannel::class) - ->args([service('mailer.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->args([ + service('mailer.transports'), + abstract_arg('message bus'), + ]) ->tag('notifier.channel', ['channel' => 'email']) ->set('notifier.channel.push', PushChannel::class) @@ -76,7 +85,7 @@ ->set('chatter', Chatter::class) ->args([ service('chatter.transports'), - service('messenger.default_bus')->ignoreOnInvalid(), + abstract_arg('message bus'), service('event_dispatcher')->ignoreOnInvalid(), ]) @@ -96,7 +105,7 @@ ->set('texter', Texter::class) ->args([ service('texter.transports'), - service('messenger.default_bus')->ignoreOnInvalid(), + abstract_arg('message bus'), service('event_dispatcher')->ignoreOnInvalid(), ]) @@ -117,7 +126,11 @@ ->args([service('texter.transports')]) ->tag('messenger.message_handler', ['handles' => PushMessage::class]) - ->set('notifier.logger_notification_listener', NotificationLoggerListener::class) + ->set('notifier.notification_logger_listener', NotificationLoggerListener::class) ->tag('kernel.event_subscriber') + + ->alias('notifier.logger_notification_listener', 'notifier.notification_logger_listener') + ->deprecate('symfony/framework-bundle', '6.3', 'The "%alias_id%" service is deprecated, use "notifier.notification_logger_listener" instead.') + ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_debug.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_debug.php index 6147d34e4e7eb..47eab26f936da 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_debug.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_debug.php @@ -16,7 +16,7 @@ return static function (ContainerConfigurator $container) { $container->services() ->set('notifier.data_collector', NotificationDataCollector::class) - ->args([service('notifier.logger_notification_listener')]) + ->args([service('notifier.notification_logger_listener')]) ->tag('data_collector', ['template' => '@WebProfiler/Collector/notifier.html.twig', 'id' => 'notifier']) ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 237ae18a59eb7..474f013f000a5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -11,58 +11,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; -use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; -use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; -use Symfony\Component\Notifier\Bridge\Chatwork\ChatworkTransportFactory; -use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; -use Symfony\Component\Notifier\Bridge\ContactEveryone\ContactEveryoneTransportFactory; -use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; -use Symfony\Component\Notifier\Bridge\Engagespot\EngagespotTransportFactory; -use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; -use Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory; -use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; -use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; -use Symfony\Component\Notifier\Bridge\FortySixElks\FortySixElksTransportFactory; -use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; -use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; -use Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; -use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; -use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; -use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; -use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; -use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; -use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; -use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; -use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; -use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; -use Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; -use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory; -use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; -use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; -use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; -use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; -use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; -use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; -use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory; -use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; -use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; -use Symfony\Component\Notifier\Bridge\Sms77\Sms77TransportFactory; -use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; -use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; -use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; -use Symfony\Component\Notifier\Bridge\SmsFactor\SmsFactorTransportFactory; -use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; -use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; -use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; -use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; -use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; -use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; -use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; -use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; -use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; +use Symfony\Component\Notifier\Bridge; use Symfony\Component\Notifier\Transport\AbstractTransportFactory; use Symfony\Component\Notifier\Transport\NullTransportFactory; @@ -73,139 +22,147 @@ ->abstract() ->args([service('event_dispatcher'), service('http_client')->ignoreOnInvalid()]) - ->set('notifier.transport_factory.slack', SlackTransportFactory::class) + ->set('notifier.transport_factory.slack', Bridge\Slack\SlackTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.linked-in', LinkedInTransportFactory::class) + ->set('notifier.transport_factory.linked-in', Bridge\LinkedIn\LinkedInTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.telegram', TelegramTransportFactory::class) + ->set('notifier.transport_factory.telegram', Bridge\Telegram\TelegramTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.mattermost', MattermostTransportFactory::class) + ->set('notifier.transport_factory.mattermost', Bridge\Mattermost\MattermostTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.vonage', VonageTransportFactory::class) + ->set('notifier.transport_factory.vonage', Bridge\Vonage\VonageTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.rocket-chat', RocketChatTransportFactory::class) + ->set('notifier.transport_factory.rocket-chat', Bridge\RocketChat\RocketChatTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.google-chat', GoogleChatTransportFactory::class) + ->set('notifier.transport_factory.google-chat', Bridge\GoogleChat\GoogleChatTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.twilio', TwilioTransportFactory::class) + ->set('notifier.transport_factory.twilio', Bridge\Twilio\TwilioTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.all-my-sms', AllMySmsTransportFactory::class) + ->set('notifier.transport_factory.twitter', Bridge\Twitter\TwitterTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.all-my-sms', Bridge\AllMySms\AllMySmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.firebase', FirebaseTransportFactory::class) + ->set('notifier.transport_factory.firebase', Bridge\Firebase\FirebaseTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.forty-six-elks', FortySixElksTransportFactory::class) + ->set('notifier.transport_factory.forty-six-elks', Bridge\FortySixElks\FortySixElksTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.free-mobile', FreeMobileTransportFactory::class) + ->set('notifier.transport_factory.free-mobile', Bridge\FreeMobile\FreeMobileTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.spot-hit', SpotHitTransportFactory::class) + ->set('notifier.transport_factory.spot-hit', Bridge\SpotHit\SpotHitTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.fake-chat', FakeChatTransportFactory::class) + ->set('notifier.transport_factory.fake-chat', Bridge\FakeChat\FakeChatTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.fake-sms', FakeSmsTransportFactory::class) + ->set('notifier.transport_factory.fake-sms', Bridge\FakeSms\FakeSmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.ovh-cloud', OvhCloudTransportFactory::class) + ->set('notifier.transport_factory.ovh-cloud', Bridge\OvhCloud\OvhCloudTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.sinch', SinchTransportFactory::class) + ->set('notifier.transport_factory.sinch', Bridge\Sinch\SinchTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.zulip', ZulipTransportFactory::class) + ->set('notifier.transport_factory.zulip', Bridge\Zulip\ZulipTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.infobip', InfobipTransportFactory::class) + ->set('notifier.transport_factory.infobip', Bridge\Infobip\InfobipTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.mobyt', MobytTransportFactory::class) + ->set('notifier.transport_factory.isendpro', Bridge\Isendpro\IsendproTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.smsapi', SmsapiTransportFactory::class) + ->set('notifier.transport_factory.mobyt', Bridge\Mobyt\MobytTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.esendex', EsendexTransportFactory::class) + ->set('notifier.transport_factory.smsapi', Bridge\Smsapi\SmsapiTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.sendberry', SendberryTransportFactory::class) + ->set('notifier.transport_factory.esendex', Bridge\Esendex\EsendexTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.sendinblue', SendinblueTransportFactory::class) + ->set('notifier.transport_factory.sendberry', Bridge\Sendberry\SendberryTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.iqsms', IqsmsTransportFactory::class) + ->set('notifier.transport_factory.sendinblue', Bridge\Sendinblue\SendinblueTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.octopush', OctopushTransportFactory::class) + ->set('notifier.transport_factory.iqsms', Bridge\Iqsms\IqsmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.discord', DiscordTransportFactory::class) + ->set('notifier.transport_factory.octopush', Bridge\Octopush\OctopushTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.discord', Bridge\Discord\DiscordTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.microsoft-teams', MicrosoftTeamsTransportFactory::class) + ->set('notifier.transport_factory.microsoft-teams', Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.gateway-api', GatewayApiTransportFactory::class) + ->set('notifier.transport_factory.gateway-api', Bridge\GatewayApi\GatewayApiTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.mercure', MercureTransportFactory::class) + ->set('notifier.transport_factory.mercure', Bridge\Mercure\MercureTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.gitter', GitterTransportFactory::class) + ->set('notifier.transport_factory.gitter', Bridge\Gitter\GitterTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.clickatell', ClickatellTransportFactory::class) + ->set('notifier.transport_factory.clickatell', Bridge\Clickatell\ClickatellTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.contact-everyone', ContactEveryoneTransportFactory::class) + ->set('notifier.transport_factory.contact-everyone', Bridge\ContactEveryone\ContactEveryoneTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.amazon-sns', AmazonSnsTransportFactory::class) + ->set('notifier.transport_factory.amazon-sns', Bridge\AmazonSns\AmazonSnsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') ->tag('chatter.transport_factory') @@ -215,77 +172,112 @@ ->tag('chatter.transport_factory') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.light-sms', LightSmsTransportFactory::class) + ->set('notifier.transport_factory.light-sms', Bridge\LightSms\LightSmsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.sms-biuras', Bridge\SmsBiuras\SmsBiurasTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.smsc', Bridge\Smsc\SmscTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.sms-factor', Bridge\SmsFactor\SmsFactorTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.message-bird', Bridge\MessageBird\MessageBirdTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.sms-biuras', SmsBiurasTransportFactory::class) + ->set('notifier.transport_factory.message-media', Bridge\MessageMedia\MessageMediaTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.smsc', SmscTransportFactory::class) + ->set('notifier.transport_factory.telnyx', Bridge\Telnyx\TelnyxTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.sms-factor', SmsFactorTransportFactory::class) + ->set('notifier.transport_factory.mailjet', Bridge\Mailjet\MailjetTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.message-bird', MessageBirdTransportFactory::class) + ->set('notifier.transport_factory.yunpian', Bridge\Yunpian\YunpianTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.message-media', MessageMediaTransportFactory::class) + ->set('notifier.transport_factory.turbo-sms', Bridge\TurboSms\TurboSmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.telnyx', TelnyxTransportFactory::class) + ->set('notifier.transport_factory.sms77', Bridge\Sms77\Sms77TransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.mailjet', MailjetTransportFactory::class) + ->set('notifier.transport_factory.one-signal', Bridge\OneSignal\OneSignalTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.yunpian', YunpianTransportFactory::class) + ->set('notifier.transport_factory.orange-sms', Bridge\OrangeSms\OrangeSmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.turbo-sms', TurboSmsTransportFactory::class) + ->set('notifier.transport_factory.expo', Bridge\Expo\ExpoTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.sms77', Sms77TransportFactory::class) + ->set('notifier.transport_factory.kaz-info-teh', Bridge\KazInfoTeh\KazInfoTehTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.one-signal', OneSignalTransportFactory::class) + ->set('notifier.transport_factory.engagespot', Bridge\Engagespot\EngagespotTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.orange-sms', OrangeSmsTransportFactory::class) + ->set('notifier.transport_factory.zendesk', Bridge\Zendesk\ZendeskTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.chatwork', Bridge\Chatwork\ChatworkTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.termii', Bridge\Termii\TermiiTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.expo', ExpoTransportFactory::class) + ->set('notifier.transport_factory.ring-central', Bridge\RingCentral\RingCentralTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.kaz-info-teh', KazInfoTehTransportFactory::class) + ->set('notifier.transport_factory.plivo', Bridge\Plivo\PlivoTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.engagespot', EngagespotTransportFactory::class) + ->set('notifier.transport_factory.bandwidth', Bridge\Bandwidth\BandwidthTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') - ->set('notifier.transport_factory.zendesk', ZendeskTransportFactory::class) + ->set('notifier.transport_factory.line-notify', Bridge\LineNotify\LineNotifyTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') - ->set('notifier.transport_factory.chatwork', ChatworkTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') + ->set('notifier.transport_factory.mastodon', Bridge\Mastodon\MastodonTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + ->set('notifier.transport_factory.pager-duty', Bridge\PagerDuty\PagerDutyTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.pushover', Bridge\Pushover\PushoverTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.simple-textin', Bridge\SimpleTextin\SimpleTextinTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_webhook.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_webhook.php new file mode 100644 index 0000000000000..87dfc6c6a08e4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_webhook.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Notifier\Bridge\Twilio\Webhook\TwilioRequestParser; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('notifier.webhook.request_parser.twilio', TwilioRequestParser::class) + ->alias(TwilioRequestParser::class, 'notifier.webhook.request_parser.twilio') + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/remote_event.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/remote_event.php new file mode 100644 index 0000000000000..41209314b224b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/remote_event.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\RemoteEvent\Messenger\ConsumeRemoteEventHandler; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('remote_event.messenger.handler', ConsumeRemoteEventHandler::class) + ->args([ + tagged_locator('remote_event.consumer', 'consumer'), + ]) + ->tag('messenger.message_handler') + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/webhook.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/webhook.xml new file mode 100644 index 0000000000000..aa71fec2d4c23 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/webhook.xml @@ -0,0 +1,10 @@ + + + + + + webhook.controller::handle + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/scheduler.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/scheduler.php new file mode 100644 index 0000000000000..9ad64c56a051d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/scheduler.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('scheduler.messenger_transport_factory', SchedulerTransportFactory::class) + ->args([ + tagged_locator('scheduler.schedule_provider', 'name'), + service('clock'), + ]) + ->tag('messenger.transport_factory') + ; +}; 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 01eeabf92cfbf..52b1f158d4391 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -10,6 +10,7 @@ + @@ -24,6 +25,7 @@ + @@ -42,6 +44,8 @@ + + @@ -183,6 +187,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -269,6 +300,10 @@ + + + + @@ -620,6 +655,7 @@ + @@ -645,6 +681,7 @@ + @@ -914,4 +951,23 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php index edf1987e32adc..6459dfa4442bd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php @@ -103,7 +103,7 @@ ->tag('serializer.normalizer', ['priority' => -950]) ->set('serializer.normalizer.problem', ProblemNormalizer::class) - ->args([param('kernel.debug')]) + ->args([param('kernel.debug'), '$translator' => service('translator')->nullOnInvalid()]) ->tag('serializer.normalizer', ['priority' => -890]) ->set('serializer.denormalizer.unwrapping', UnwrappingDenormalizer::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php index 9facc09c783f8..6d805bfbc7dd1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php @@ -15,8 +15,8 @@ use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; use Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer; use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; +use Symfony\Component\Clock\Clock; use Symfony\Component\Clock\ClockInterface; -use Symfony\Component\Clock\NativeClock; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; use Symfony\Component\Config\ResourceCheckerConfigCacheFactory; @@ -229,7 +229,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] ->args([service(KernelInterface::class), service('logger')->nullOnInvalid()]) ->tag('kernel.cache_warmer') - ->set('clock', NativeClock::class) + ->set('clock', Clock::class) ->alias(ClockInterface::class, 'clock') ->alias(PsrClockInterface::class, 'clock') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php index 1c896de3a89df..6c5b86b2ee646 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php @@ -16,6 +16,7 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Validator\Constraints\EmailValidator; use Symfony\Component\Validator\Constraints\ExpressionValidator; +use Symfony\Component\Validator\Constraints\NoSuspiciousCharactersValidator; use Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator; use Symfony\Component\Validator\Constraints\WhenValidator; use Symfony\Component\Validator\ContainerConstraintValidatorFactory; @@ -104,6 +105,12 @@ ->args([service('validator.expression_language')->nullOnInvalid()]) ->tag('validator.constraint_validator') + ->set('validator.no_suspicious_characters', NoSuspiciousCharactersValidator::class) + ->args([param('kernel.enabled_locales')]) + ->tag('validator.constraint_validator', [ + 'alias' => NoSuspiciousCharactersValidator::class, + ]) + ->set('validator.property_info_loader', PropertyInfoLoader::class) ->args([ service('property_info'), diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php index 82ceb2e077da4..1044ded974721 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php @@ -16,7 +16,9 @@ use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\QueryParameterValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; @@ -46,37 +48,52 @@ ->args([ service('argument_metadata_factory'), abstract_arg('argument value resolvers'), + abstract_arg('targeted value resolvers'), ]) ->set('argument_resolver.backed_enum_resolver', BackedEnumValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => 100]) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => BackedEnumValueResolver::class]) ->set('argument_resolver.uid', UidValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => 100]) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => UidValueResolver::class]) ->set('argument_resolver.datetime', DateTimeValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => 100]) + ->args([ + service('clock')->nullOnInvalid(), + ]) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => DateTimeValueResolver::class]) + + ->set('argument_resolver.request_payload', RequestPayloadValueResolver::class) + ->args([ + service('serializer'), + service('validator')->nullOnInvalid(), + service('translator')->nullOnInvalid(), + ]) + ->tag('controller.targeted_value_resolver', ['name' => RequestPayloadValueResolver::class]) ->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => 100]) + ->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => RequestAttributeValueResolver::class]) ->set('argument_resolver.request', RequestValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => 50]) + ->tag('controller.argument_value_resolver', ['priority' => 50, 'name' => RequestValueResolver::class]) ->set('argument_resolver.session', SessionValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => 50]) + ->tag('controller.argument_value_resolver', ['priority' => 50, 'name' => SessionValueResolver::class]) ->set('argument_resolver.service', ServiceValueResolver::class) ->args([ abstract_arg('service locator, set in RegisterControllerArgumentLocatorsPass'), ]) - ->tag('controller.argument_value_resolver', ['priority' => -50]) + ->tag('controller.argument_value_resolver', ['priority' => -50, 'name' => ServiceValueResolver::class]) ->set('argument_resolver.default', DefaultValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => -100]) + ->tag('controller.argument_value_resolver', ['priority' => -100, 'name' => DefaultValueResolver::class]) ->set('argument_resolver.variadic', VariadicValueResolver::class) - ->tag('controller.argument_value_resolver', ['priority' => -150]) + ->tag('controller.argument_value_resolver', ['priority' => -150, 'name' => VariadicValueResolver::class]) + + ->set('argument_resolver.query_parameter_value_resolver', QueryParameterValueResolver::class) + ->tag('controller.targeted_value_resolver', ['name' => QueryParameterValueResolver::class]) ->set('response_listener', ResponseListener::class) ->args([ diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web_link.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web_link.php index 0b0e79db8c1bf..64345cc997717 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web_link.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web_link.php @@ -12,10 +12,18 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; +use Symfony\Component\WebLink\HttpHeaderSerializer; return static function (ContainerConfigurator $container) { $container->services() + + ->set('web_link.http_header_serializer', HttpHeaderSerializer::class) + ->alias(HttpHeaderSerializer::class, 'web_link.http_header_serializer') + ->set('web_link.add_link_header_listener', AddLinkHeaderListener::class) + ->args([ + service('web_link.http_header_serializer'), + ]) ->tag('kernel.event_subscriber') ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/webhook.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/webhook.php new file mode 100644 index 0000000000000..a7e9d58ce9a65 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/webhook.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Webhook\Client\RequestParser; +use Symfony\Component\Webhook\Controller\WebhookController; +use Symfony\Component\Webhook\Messenger\SendWebhookHandler; +use Symfony\Component\Webhook\Server\HeadersConfigurator; +use Symfony\Component\Webhook\Server\HeaderSignatureConfigurator; +use Symfony\Component\Webhook\Server\JsonBodyConfigurator; +use Symfony\Component\Webhook\Server\Transport; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('webhook.transport', Transport::class) + ->args([ + service('http_client'), + service('webhook.headers_configurator'), + service('webhook.body_configurator.json'), + service('webhook.signer'), + ]) + + ->set('webhook.headers_configurator', HeadersConfigurator::class) + + ->set('webhook.body_configurator.json', JsonBodyConfigurator::class) + ->args([ + service('serializer'), + ]) + + ->set('webhook.signer', HeaderSignatureConfigurator::class) + + ->set('webhook.messenger.send_handler', SendWebhookHandler::class) + ->args([ + service('webhook.transport'), + ]) + ->tag('messenger.message_handler') + + ->set('webhook.request_parser', RequestParser::class) + ->alias(RequestParser::class, 'webhook.request_parser') + + ->set('webhook.controller', WebhookController::class) + ->public() + ->args([ + abstract_arg('user defined parsers'), + abstract_arg('message bus'), + ]) + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php index 7e6fa732090eb..ec03f849793df 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php @@ -24,6 +24,8 @@ class AnnotatedRouteControllerLoader extends AnnotationClassLoader { /** * Configures the _controller default parameter of a given Route instance. + * + * @return void */ protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index ac5db387de808..f9747a3c489a8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -109,7 +109,7 @@ public function warmUp(string $cacheDir): array * - the route schemes, * - the route methods. */ - private function resolveParameters(RouteCollection $collection) + private function resolveParameters(RouteCollection $collection): void { foreach ($collection as $route) { foreach ($route->getDefaults() as $name => $value) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php index cfa4bab13c3a6..b3eb0c6bc337c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php +++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php @@ -40,6 +40,9 @@ protected function validateName(string $name): void } } + /** + * @return string + */ protected function getPrettyPath(string $path) { return str_replace(getcwd().\DIRECTORY_SEPARATOR, '', $path); diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php index 6e8247cbf54e4..02e3ad848b1ae 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php @@ -34,6 +34,11 @@ public static function assertSelectorNotExists(string $selector, string $message self::assertThat(self::getCrawler(), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorExists($selector)), $message); } + public static function assertSelectorCount(int $expectedCount, string $selector, string $message = ''): void + { + self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorCount($expectedCount, $selector), $message); + } + public static function assertSelectorTextContains(string $selector, string $text, string $message = ''): void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php index 163c2a9719c55..30298ef04c54f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php @@ -91,8 +91,8 @@ public static function getNotifierMessage(int $index = 0, string $transportName public static function getNotificationEvents(): NotificationEvents { $container = static::getContainer(); - if ($container->has('notifier.logger_notification_listener')) { - return $container->get('notifier.logger_notification_listener')->getEvents(); + if ($container->has('notifier.notification_logger_listener')) { + return $container->get('notifier.notification_logger_listener')->getEvents(); } static::fail('A client must have Notifier enabled to make notifications assertions. Did you forget to require symfony/notifier?'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php index 883928087ea03..e1e7a85926068 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php @@ -13,12 +13,13 @@ use Psr\Container\ContainerInterface; use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpKernel\KernelInterface; /** * A special container used in tests. This gives access to both public and - * private services. The container will not include private services that has + * private services. The container will not include private services that have * been inlined or removed. Private services will be removed when they are not * used by other services. * @@ -28,16 +29,14 @@ */ class TestContainer extends Container { - private KernelInterface $kernel; - private string $privateServicesLocatorId; - - public function __construct(KernelInterface $kernel, string $privateServicesLocatorId) - { - $this->kernel = $kernel; - $this->privateServicesLocatorId = $privateServicesLocatorId; + public function __construct( + private KernelInterface $kernel, + private string $privateServicesLocatorId, + private array $renamedIds = [], + ) { } - public function compile() + public function compile(): void { $this->getPublicContainer()->compile(); } @@ -62,14 +61,27 @@ public function hasParameter(string $name): bool return $this->getPublicContainer()->hasParameter($name); } - public function setParameter(string $name, mixed $value) + public function setParameter(string $name, mixed $value): void { $this->getPublicContainer()->setParameter($name, $value); } - public function set(string $id, mixed $service) + public function set(string $id, mixed $service): void { - $this->getPublicContainer()->set($id, $service); + $container = $this->getPublicContainer(); + $renamedId = $this->renamedIds[$id] ?? $id; + + try { + $container->set($renamedId, $service); + } catch (InvalidArgumentException $e) { + if (!str_starts_with($e->getMessage(), "The \"$renamedId\" service is private")) { + throw $e; + } + if (isset($container->privates[$renamedId])) { + throw new InvalidArgumentException(sprintf('The "%s" service is already initialized, you cannot replace it.', $id)); + } + $container->privates[$renamedId] = $service; + } } public function has(string $id): bool @@ -87,7 +99,7 @@ public function initialized(string $id): bool return $this->getPublicContainer()->initialized($id); } - public function reset() + public function reset(): void { // ignore the call } @@ -104,11 +116,7 @@ public function getRemovedIds(): array private function getPublicContainer(): Container { - if (null === $container = $this->kernel->getContainer()) { - throw new \LogicException('Cannot access the container on a non-booted kernel. Did you forget to boot it?'); - } - - return $container; + return $this->kernel->getContainer() ?? throw new \LogicException('Cannot access the container on a non-booted kernel. Did you forget to boot it?'); } private function getPrivateContainer(): ContainerInterface diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/AboutCommand/Fixture/TestAppKernel.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/AboutCommand/Fixture/TestAppKernel.php index cc55da770ed2d..4d4810d286005 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/AboutCommand/Fixture/TestAppKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/AboutCommand/Fixture/TestAppKernel.php @@ -30,7 +30,7 @@ public function getProjectDir(): string return __DIR__.'/test'; } - public function registerContainerConfiguration(LoaderInterface $loader) + public function registerContainerConfiguration(LoaderInterface $loader): void { $loader->load(static function (ContainerBuilder $container) { $container->loadFromExtension('framework', [ @@ -39,7 +39,7 @@ public function registerContainerConfiguration(LoaderInterface $loader) }); } - protected function build(ContainerBuilder $container) + protected function build(ContainerBuilder $container): void { } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php index 678d573586f3a..4522a7d59d8f3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php @@ -31,12 +31,12 @@ public function getProjectDir(): string return __DIR__.'/test'; } - public function registerContainerConfiguration(LoaderInterface $loader) + public function registerContainerConfiguration(LoaderInterface $loader): void { $loader->load(__DIR__.\DIRECTORY_SEPARATOR.'config.yml'); } - protected function build(ContainerBuilder $container) + protected function build(ContainerBuilder $container): void { $container->register('logger', NullLogger::class); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php index 983956fcb9b82..54467f1efe879 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php @@ -45,9 +45,7 @@ private function getRewindableGenerator(): RewindableGenerator private function getEmptyRewindableGenerator(): RewindableGenerator { - return new RewindableGenerator(function () { - return new \ArrayIterator([]); - }, 0); + return new RewindableGenerator(fn () => new \ArrayIterator([]), 0); } private function getKernel(): MockObject&KernelInterface diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/EventDispatcherDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/EventDispatcherDebugCommandTest.php index 9bc054f22a942..331d8d44a52f8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/EventDispatcherDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/EventDispatcherDebugCommandTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Mailer\Event\MessageEvent; class EventDispatcherDebugCommandTest extends TestCase { @@ -33,7 +34,7 @@ public function testComplete(array $input, array $expectedSuggestions) public static function provideCompletionSuggestions() { - yield 'event' => [[''], ['Symfony\Component\Mailer\Event\MessageEvent', 'console.command']]; + yield 'event' => [[''], [MessageEvent::class, 'console.command']]; yield 'event for other dispatcher' => [['--dispatcher', 'other_event_dispatcher', ''], ['other_event', 'App\OtherEvent']]; yield 'dispatcher' => [['--dispatcher='], ['event_dispatcher', 'other_event_dispatcher']]; yield 'format' => [['--format='], ['txt', 'xml', 'json', 'md']]; @@ -44,7 +45,7 @@ private function createCommandCompletionTester(): CommandCompletionTester $dispatchers = new ServiceLocator([ 'event_dispatcher' => function () { $dispatcher = new EventDispatcher(); - $dispatcher->addListener('Symfony\Component\Mailer\Event\MessageEvent', 'var_dump'); + $dispatcher->addListener(MessageEvent::class, 'var_dump'); $dispatcher->addListener('console.command', 'var_dump'); return $dispatcher; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php index f5af74b98ea5f..7dab41991b1b1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php @@ -76,9 +76,7 @@ private function getKernel() $container ->expects($this->atLeastOnce()) ->method('has') - ->willReturnCallback(function ($id) { - return 'console.command_loader' !== $id; - }) + ->willReturnCallback(fn ($id) => 'console.command_loader' !== $id) ; $container ->expects($this->any()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php index 94fcbcfa3bcd3..daafc6011d3d0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php @@ -243,7 +243,7 @@ public static function getEventDispatchers() $eventDispatcher = new EventDispatcher(); $eventDispatcher->addListener('event1', 'var_dump', 255); - $eventDispatcher->addListener('event1', function () { return 'Closure'; }, -1); + $eventDispatcher->addListener('event1', fn () => 'Closure', -1); $eventDispatcher->addListener('event2', new CallableClass()); return ['event_dispatcher_1' => $eventDispatcher]; @@ -256,7 +256,7 @@ public static function getCallables(): array 'callable_2' => ['Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\CallableClass', 'staticMethod'], 'callable_3' => [new CallableClass(), 'method'], 'callable_4' => 'Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\CallableClass::staticMethod', - 'callable_6' => function () { return 'Closure'; }, + 'callable_6' => fn () => 'Closure', 'callable_7' => new CallableClass(), 'callable_from_callable' => (new CallableClass())(...), ]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/TextDescriptorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/TextDescriptorTest.php index 55e410597344a..340defd8e61fa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/TextDescriptorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/TextDescriptorTest.php @@ -17,7 +17,7 @@ class TextDescriptorTest extends AbstractDescriptorTestCase { - private static $fileLinkFormatter = null; + private static $fileLinkFormatter; protected static function getDescriptor() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php index efdba819cdbdc..960735e884958 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php @@ -45,6 +45,7 @@ use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\WebLink\Link; use Twig\Environment; @@ -72,6 +73,8 @@ public function testSubscribedServices() 'parameter_bag' => '?Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface', 'security.token_storage' => '?Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface', 'security.csrf.token_manager' => '?Symfony\\Component\\Security\\Csrf\\CsrfTokenManagerInterface', + 'web_link.http_header_serializer' => '?Symfony\\Component\\WebLink\\HttpHeaderSerializer', + 'asset_mapper.importmap.manager' => '?Symfony\\Component\\AssetMapper\\ImportMap\\ImportMapManager', ]; $this->assertEquals($expectedServices, $subscribed, 'Subscribed core services in AbstractController have changed'); @@ -110,9 +113,7 10000 @@ public function testForward() $requestStack->push($request); $kernel = $this->createMock(HttpKernelInterface::class); - $kernel->expects($this->once())->method('handle')->willReturnCallback(function (Request $request) { - return new Response($request->getRequestFormat().'--'.$request->getLocale()); - }); + $kernel->expects($this->once())->method('handle')->willReturnCallback(fn (Request $request) => new Response($request->getRequestFormat().'--'.$request->getLocale())); $container = new Container(); $container->set('request_stack', $requestStack); @@ -679,4 +680,20 @@ public function testAddLink() $this->assertContains($link1, $links); $this->assertContains($link2, $links); } + + public function testSendEarlyHints() + { + $container = new Container(); + $container->set('web_link.http_header_serializer', new HttpHeaderSerializer()); + + $controller = $this->createController(); + $controller->setContainer($container); + + $response = $controller->sendEarlyHints([ + (new Link(href: '/style.css'))->withAttribute('as', 'stylesheet'), + (new Link(href: '/script.js'))->withAttribute('as', 'script'), + ]); + + $this->assertSame('; rel="preload"; as="stylesheet",; rel="preload"; as="script"', $response->headers->get('Link')); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php index 6f5b870e3350e..2c042917acc85 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php @@ -170,14 +170,14 @@ protected function createMockContainer() class ContainerAwareController implements ContainerAwareInterface { - private $container; + private ?ContainerInterface $container = null; - public function setContainer(?ContainerInterface $container) + public function setContainer(?ContainerInterface $container): void { $this->container = $container; } - public function getContainer() + public function getContainer(): ?ContainerInterface { return $this->container; } @@ -193,7 +193,7 @@ public function __invoke() class DummyController extends AbstractController { - public function getContainer() + public function getContainer(): Psr11ContainerInterface { return $this->container; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/TemplateControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/TemplateControllerTest.php index e567342ecf4c9..f558333f36a40 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/TemplateControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/TemplateControllerTest.php @@ -35,7 +35,7 @@ public function testTwig() public function testNoTwig() { $this->expectException(\LogicException::class); - $this->expectExceptionMessage('You cannot use the TemplateController if the Twig Bundle is not available.'); + $this->expectExceptionMessage('You cannot use the TemplateController if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); $controller = new TemplateController(); $controller->templateAction('mytemplate')->getContent(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php index 1207a4820d103..aaba736255f1d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php @@ -24,11 +24,11 @@ public function testProcessForRouter() $container = new ContainerBuilder(); $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); - $definition = new Definition('\stdClass'); + $definition = new Definition(\stdClass::class); $definition->addTag('routing.expression_language_provider'); $container->setDefinition('some_routing_provider', $definition->setPublic(true)); - $container->register('router.default', '\stdClass')->setPublic(true); + $container->register('router.default', \stdClass::class)->setPublic(true); $container->compile(); $router = $container->getDefinition('router.default'); @@ -43,11 +43,11 @@ public function testProcessForRouterAlias() $container = new ContainerBuilder(); $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); - $definition = new Definition('\stdClass'); + $definition = new Definition(\stdClass::class); $definition->addTag('routing.expression_language_provider'); $container->setDefinition('some_routing_provider', $definition->setPublic(true)); - $container->register('my_router', '\stdClass')->setPublic(true); + $container->register('my_router', \stdClass::class)->setPublic(true); $container->setAlias('router.default', 'my_router'); $container->compile(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php index a5d58edbb4226..8601f8c81ad2c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php @@ -15,6 +15,9 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Translation\DataCollector\TranslationDataCollector; +use Symfony\Component\Translation\DataCollectorTranslator; +use Symfony\Component\Translation\Translator; use Symfony\Contracts\Translation\TranslatorInterface; class DataCollectorTranslatorPassTest extends TestCase @@ -27,16 +30,16 @@ protected function setUp(): void $this->container = new ContainerBuilder(); $this->dataCollectorTranslatorPass = new DataCollectorTranslatorPass(); - $this->container->setParameter('translator_implementing_bag', 'Symfony\Component\Translation\Translator'); + $this->container->setParameter('translator_implementing_bag', Translator::class); $this->container->setParameter('translator_not_implementing_bag', 'Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\TranslatorWithTranslatorBag'); - $this->container->register('translator.data_collector', 'Symfony\Component\Translation\DataCollectorTranslator') + $this->container->register('translator.data_collector', DataCollectorTranslator::class) ->setPublic(false) ->setDecoratedService('translator') ->setArguments([new Reference('translator.data_collector.inner')]) ; - $this->container->register('data_collector.translation', 'Symfony\Component\Translation\DataCollector\TranslationDataCollector') + $this->container->register('data_collector.translation', TranslationDataCollector::class) ->setArguments([new Reference('translator.data_collector')]) ; } @@ -68,7 +71,7 @@ public function testProcessKeepsDataCollectorIfTranslatorImplementsTranslatorBag public static function getImplementingTranslatorBagInterfaceTranslatorClassNames() { return [ - ['Symfony\Component\Translation\Translator'], + [Translator::class], ['%translator_implementing_bag%'], ]; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php index 6838d47883d77..9204212169a46 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php @@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Translation\Translator; class LoggingTranslatorPassTest extends TestCase { @@ -22,7 +23,7 @@ public function testProcess() { $container = new ContainerBuilder(); $container->setParameter('translator.logging', true); - $container->setParameter('translator.class', 'Symfony\Component\Translation\Translator'); + $container->setParameter('translator.class', Translator::class); $container->register('monolog.logger'); $container->setAlias('logger', 'monolog.logger'); $container->register('translator.default', '%translator.class%'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php index 4844b4e9a922a..eef047dfa8c29 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php @@ -65,7 +65,7 @@ public function testValidCollector() public static function provideValidCollectorWithTemplateUsingAutoconfigure(): \Generator { yield [new class() implements TemplateAwareDataCollectorInterface { - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { } @@ -74,7 +74,7 @@ public function getName(): string return static::class; } - public function reset() + public function reset(): void { } @@ -85,7 +85,7 @@ public static function getTemplate(): string }]; yield [new class() extends AbstractDataCollector { - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php index 355b1527d64bf..fc69d5bd16858 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; @@ -67,7 +68,7 @@ public function testProcess() ]; $privateServices = $container->getDefinition('test.private_services_locator')->getArgument(0); - unset($privateServices['Symfony\Component\DependencyInjection\ContainerInterface'], $privateServices['Psr\Container\ContainerInterface']); + unset($privateServices[\Symfony\Component\DependencyInjection\ContainerInterface::class], $privateServices[ContainerInterface::class]); $this->assertEquals($expected, $privateServices); $this->assertFalse($container->getDefinition('Test\private_used_non_shared_service')->isShared()); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php index 433b798d81a94..46dd94b86b3a4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php @@ -44,9 +44,7 @@ public function testMissingKnownTags() private function getKnownTags(): array { $tags = \Closure::bind( - static function () { - return UnusedTagsPass::KNOWN_TAGS; - }, + static fn () => UnusedTagsPass::KNOWN_TAGS, null, UnusedTagsPass::class )(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index a9917f69cc42b..e91d32a68e74e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -26,6 +26,7 @@ use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Notifier\Notifier; use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; +use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory; use Symfony\Component\Uid\Factory\UuidFactory; class ConfigurationTest extends TestCase @@ -94,6 +95,29 @@ public function testAssetsCanBeEnabled() $this->assertEquals($defaultConfig, $config['assets']); } + public function testAssetMapperCanBeEnabled() + { + $processor = new Processor(); + $configuration = new Configuration(true); + $config = $processor->processConfiguration($configuration, [['http_method_override' => false, 'asset_mapper' => null]]); + + $defaultConfig = [ + 'enabled' => true, + 'paths' => [], + 'server' => true, + 'public_prefix' => '/assets/', + 'strict_mode' => true, + 'extensions' => [], + 'importmap_path' => '%kernel.project_dir%/importmap.php', + 'importmap_polyfill' => null, + 'vendor_dir' => '%kernel.project_dir%/assets/vendor', + 'provider' => 'jspm', + 'importmap_script_attributes' => [], + ]; + + $this->assertEquals($defaultConfig, $config['asset_mapper']); + } + /** * @dataProvider provideValidAssetsPackageNameConfigurationTests */ @@ -145,15 +169,13 @@ public function testInvalidAssetsConfiguration(array $assetConfig, $expectedMess public static function provideInvalidAssetConfigurationTests() { // helper to turn config into embedded package config - $createPackageConfig = function (array $packageConfig) { - return [ - 'base_urls' => '//example.com', - 'version' => 1, - 'packages' => [ - 'foo' => $packageConfig, - ], - ]; - }; + $createPackageConfig = fn (array $packageConfig) => [ + 'base_urls' => '//example.com', + 'version' => 1, + 'packages' => [ + 'foo' => $packageConfig, + ], + ]; $config = [ 'version' => 1, @@ -590,6 +612,19 @@ protected static function getBundleDefaultConfig() 'json_manifest_path' => null, 'strict_mode' => false, ], + 'asset_mapper' => [ + 'enabled' => !class_exists(FullStack::class), + 'paths' => [], + 'server' => true, + 'public_prefix' => '/assets/', + 'strict_mode' => true, + 'extensions' => [], + 'importmap_path' => '%kernel.project_dir%/importmap.php', + 'importmap_polyfill' => null, + 'vendor_dir' => '%kernel.project_dir%/assets/vendor', + 'provider' => 'jspm', + 'importmap_script_attributes' => [], + ], 'cache' => [ 'pools' => [], 'app' => 'cache.adapter.filesystem', @@ -640,6 +675,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'default_bus' => null, 'buses' => ['messenger.bus.default' => ['default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => []]], 'reset_on_message' => true, + 'stop_worker_on_signals' => [], ], 'disallow_search_engine_index' => true, 'http_client' => [ @@ -655,6 +691,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor ], 'notifier' => [ 'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class), + 'message_bus' => null, 'chatter_transports' => [], 'texter_transports' => [], 'channel_policy' => [], @@ -672,6 +709,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'enabled' => false, 'debug' => '%kernel.debug%', 'private_headers' => [], + 'skip_response_headers' => [], ], 'rate_limiter' => [ 'enabled' => !class_exists(FullStack::class) && class_exists(TokenBucketLimiter::class), @@ -687,7 +725,18 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'enabled' => !class_exists(FullStack::class) && class_exists(HtmlSanitizer::class), 'sanitizers' => [], ], + 'scheduler' => [ + 'enabled' => !class_exists(FullStack::class) && class_exists(SchedulerTransportFactory::class), + ], 'exceptions' => [], + 'webhook' => [ + 'enabled' => false, + 'routing' => [], + 'message_bus' => 'messenger.default_bus', + ], + 'remote-event' => [ + 'enabled' => false, + ], ]; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php index 865ddd14e1203..99fa25e498678 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php @@ -24,6 +24,7 @@ 'pin-sha256' => ['14s5erg62v1v8471g2revg48r7==', 'jsda84hjtyd4821bgfesd215bsfg5412='], 'md5' => 'sdhtb481248721thbr=', ], + 'extra' => ['foo' => ['bar' => 'baz']], ], ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php index c66ce8851b42e..9880dd8e7dc15 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php @@ -6,11 +6,13 @@ 'max_host_connections' => 4, 'default_options' => [ 'headers' => ['foo' => 'bar'], + 'extra' => ['foo' => 'bar'], ], 'scoped_clients' => [ 'foo' => [ 'base_uri' => 'http://example.com', 'headers' => ['bar' => 'baz'], + 'extra' => ['bar' => 'baz'], ], ], ], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger.php index dc22cd5ff8917..6285a3b894c9f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger.php @@ -5,6 +5,7 @@ $container->loadFromExtension('framework', [ 'http_method_override' => false, + 'scheduler' => true, 'messenger' => [ 'routing' => [ FooMessage::class => ['sender.bar', 'sender.biz'], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_disabled.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_disabled.php index d14d6e94b617e..6e099cc0d65dc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_disabled.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_disabled.php @@ -3,4 +3,5 @@ $container->loadFromExtension('framework', [ 'http_method_override' => false, 'messenger' => false, + 'scheduler' => false, ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php index 77f4d5b93bebc..ef1d09e893194 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php @@ -1,5 +1,8 @@ loadFromExtension('framework', [ 'http_method_override' => false, 'serializer' => true, @@ -8,10 +11,11 @@ 'default_serializer' => 'messenger.transport.symfony_serializer', ], 'routing' => [ - 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage' => ['amqp', 'messenger.transport.audit'], - 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage' => [ + DummyMessage::class => ['amqp', 'messenger.transport.audit'], + SecondMessage::class => [ 'senders' => ['amqp', 'audit'], ], + 'Symfony\*' => 'amqp', '*' => 'amqp', ], 'transports' => [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_transport.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_transport.php index b552a3ebe5d5b..9bf91fd303d3a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_transport.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_transport.php @@ -1,5 +1,7 @@ loadFromExtension('framework', [ 'http_method_override' => false, 'serializer' => true, @@ -8,7 +10,7 @@ 'default_serializer' => 'messenger.transport.symfony_serializer', ], 'routing' => [ - 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage' => 'invalid', + DummyMessage::class => 'invalid', ], 'transports' => [ 'amqp' => 'amqp://localhost/%2f/messages', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_wildcard.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_wildcard.php new file mode 100644 index 0000000000000..c8e046ef64ca4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_invalid_wildcard.php @@ -0,0 +1,17 @@ +loadFromExtension('framework', [ + 'http_method_override' => false, + 'serializer' => true, + 'messenger' => [ + 'serializer' => [ + 'default_serializer' => 'messenger.transport.symfony_serializer', + ], + 'routing' => [ + 'Symfony\*\DummyMessage' => ['audit'], + ], + 'transports' => [ + 'audit' => 'null://', + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_single.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_single.php index f487a0f8f90d9..a40347006646e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_single.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing_single.php @@ -1,10 +1,12 @@ loadFromExtension('framework', [ 'http_method_override' => false, 'messenger' => [ 'routing' => [ - 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage' => ['amqp'], + DummyMessage::class => ['amqp'], ], 'transports' => [ 'amqp' => 'amqp://localhost/%2f/messages', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php index 19f22f2c78c99..dc94f2907e254 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php @@ -25,6 +25,7 @@ 'failed' => 'in-memory:///', 'redis' => 'redis://127.0.0.1:6379/messages', 'beanstalkd' => 'beanstalkd://127.0.0.1:11300', + 'schedule' => 'schedule://default', ], ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php new file mode 100644 index 0000000000000..88bc913c9d9b6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_disabled_message_bus.php @@ -0,0 +1,20 @@ +loadFromExtension('framework', [ + 'http_method_override' => false, + 'messenger' => [ + 'enabled' => true, + ], + 'mailer' => [ + 'dsn' => 'smtp://example.com', + ], + 'notifier' => [ + 'message_bus' => false, + 'chatter_transports' => [ + 'test' => 'null' + ], + 'texter_transports' => [ + 'test' => 'null' + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php new file mode 100644 index 0000000000000..2413edc1aeab1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_with_specific_message_bus.php @@ -0,0 +1,20 @@ +loadFromExtension('framework', [ + 'http_method_override' => false, + 'messenger' => [ + 'enabled' => true, + ], + 'mailer' => [ + 'dsn' => 'smtp://example.com', + ], + 'notifier' => [ + 'message_bus' => 'app.another_bus', + 'chatter_transports' => [ + 'test' => 'null' + ], + 'texter_transports' => [ + 'test' => 'null' + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_without_messenger.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_without_messenger.php index bc4280b97a551..5ab0199a2437f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_without_messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/notifier_without_messenger.php @@ -28,4 +28,5 @@ ['email' => 'test@test.de', 'phone' => '+490815',], ] ], + 'scheduler' => false, ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/asset_mapper.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/asset_mapper.xml new file mode 100644 index 0000000000000..ebde46f585e2b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/asset_mapper.xml @@ -0,0 +1,24 @@ + + + + + + + assets/ + assets2/ + true + /assets_path/ + true + application/zip + %kernel.project_dir%/importmap.php + https://cdn.example.com/polyfill.js + reload + %kernel.project_dir%/assets/vendor + jspm + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml index e897df725a93a..8d51a883927b0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml @@ -30,6 +30,11 @@ jsda84hjtyd4821bgfesd215bsfg5412= sdhtb481248721thbr= + + + baz + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml index fdee9a9132a35..412d937444a6c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml @@ -9,9 +9,15 @@ bar + + bar + baz + + baz + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger.xml index fef09b934a3aa..90df7ec51352d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger.xml @@ -6,6 +6,7 @@ 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/messenger_disabled.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_disabled.xml index 513842ece8392..ec3d01e230d10 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_disabled.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_disabled.xml @@ -7,5 +7,6 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml index 30b249b415c31..960939ad3916f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml @@ -20,6 +20,9 @@ + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing_invalid_wildcard.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing_invalid_wildcard.xml new file mode 100644 index 0000000000000..d6a07d6bad74e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing_invalid_wildcard.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_schedule.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_schedule.xml new file mode 100644 index 0000000000000..a5fab75c9d381 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_schedule.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml index 28e27e380bfe0..ea623dab282bb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml @@ -21,6 +21,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml new file mode 100644 index 0000000000000..3b8d00b146519 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_disabled_message_bus.xml @@ -0,0 +1,17 @@ + + + + + + + + + null + null + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml new file mode 100644 index 0000000000000..abe55d16ad50c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_with_specific_message_bus.xml @@ -0,0 +1,17 @@ + + + + + + + + + null + null + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_without_messenger.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_without_messenger.xml index 7d65d064e9947..8fd6df87c6dee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_without_messenger.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/notifier_without_messenger.xml @@ -17,5 +17,6 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml index de9300e17f158..acd33293fbe65 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml @@ -22,3 +22,6 @@ framework: peer_fingerprint: pin-sha256: ['14s5erg62v1v8471g2revg48r7==', 'jsda84hjtyd4821bgfesd215bsfg5412='] md5: 'sdhtb481248721thbr=' + extra: + foo: + bar: 'baz' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml index 14a6915380dba..3f4af70018f6a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml @@ -4,7 +4,9 @@ framework: max_host_connections: 4 default_options: headers: {'foo': 'bar'} + extra: {'foo': 'bar'} scoped_clients: foo: base_uri: http://example.com headers: {'bar': 'baz'} + extra: {'bar': 'baz'} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger.yml index 29174a9b407f2..929b1230e8a6c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger.yml @@ -1,5 +1,6 @@ framework: http_method_override: false + scheduler: true messenger: routing: 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\FooMessage': ['sender.bar', 'sender.biz'] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_disabled.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_disabled.yml index 8bbd594e3839b..6f90172804408 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_disabled.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_disabled.yml @@ -1,3 +1,4 @@ framework: http_method_override: false messenger: false + scheduler: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml index bfd682c706fbe..c064c8c5ba2b7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml @@ -8,6 +8,8 @@ framework: 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage': [amqp, messenger.transport.audit] 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\SecondMessage': senders: [amqp, audit] + 'Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\*': [amqp] + 'Symfony\*': [amqp] '*': amqp transports: amqp: 'amqp://localhost/%2f/messages' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing_invalid_wildcard.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing_invalid_wildcard.yml new file mode 100644 index 0000000000000..af5a0d22218a7 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing_invalid_wildcard.yml @@ -0,0 +1,10 @@ +framework: + http_method_override: false + serializer: true + messenger: + serializer: + default_serializer: messenger.transport.symfony_serializer + routing: + 'Symfony\*\DummyMessage': [audit] + transports: + audit: 'null://' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_schedule.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_schedule.yml new file mode 100644 index 0000000000000..d0b4c33e41870 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_schedule.yml @@ -0,0 +1,5 @@ +framework: + http_method_override: false + messenger: + transports: + schedule: 'schedule://default' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml index 24471939c5435..15728009e57ef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml @@ -22,3 +22,4 @@ framework: failed: 'in-memory:///' redis: 'redis://127.0.0.1:6379/messages' beanstalkd: 'beanstalkd://127.0.0.1:11300' + schedule: 'schedule://default' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml new file mode 100644 index 0000000000000..7790875c66f93 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_disabled_message_bus.yml @@ -0,0 +1,12 @@ +framework: + http_method_override: false + messenger: + enabled: true + mailer: + dsn: 'smtp://example.com' + notifier: + message_bus: false + chatter_transports: + test: 'null' + texter_transports: + test: 'null' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml new file mode 100644 index 0000000000000..adf4133857b06 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_with_specific_message_bus.yml @@ -0,0 +1,12 @@ +framework: + http_method_override: false + messenger: + enabled: true + mailer: + dsn: 'smtp://example.com' + notifier: + message_bus: 'app.another_bus' + chatter_transports: + test: 'null' + texter_transports: + test: 'null' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_without_messenger.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_without_messenger.yml index 9ff92d0ac74af..c55bc0358c89b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_without_messenger.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/notifier_without_messenger.yml @@ -16,3 +16,4 @@ framework: high: ['slack', 'twilio'] admin_recipients: - { email: 'test@test.de', phone: '+490815' } + scheduler: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 0597ee1dfb265..fe3b773186218 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -17,6 +17,7 @@ use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Bundle\FullStack; @@ -56,6 +57,10 @@ use Symfony\Component\HttpClient\ScopingHttpClient; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; +use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; +use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; +use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; +use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory; use Symfony\Component\Messenger\Transport\TransportFactory; use Symfony\Component\Notifier\ChatterInterface; use Symfony\Component\Notifier\TexterInterface; @@ -70,6 +75,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\DependencyInjection\TranslatorPass; use Symfony\Component\Translation\LocaleSwitcher; @@ -539,9 +545,9 @@ public function testEnabledPhpErrorsConfig() { $container = $this->createContainerFromFile('php_errors_enabled'); - $definition = $container->getDefinition('debug.debug_handlers_listener'); - $this->assertEquals(new Reference('monolog.logger.php', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(1)); - $this->assertNull($definition->getArgument(2)); + $definition = $container->getDefinition('debug.error_handler_configurator'); + $this->assertEquals(new Reference('monolog.logger.php', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(0)); + $this->assertNull($definition->getArgument(1)); $this->assertSame(-1, $container->getParameter('debug.error_handler.throw_at')); } @@ -549,9 +555,9 @@ public function testDisabledPhpErrorsConfig() { $container = $this->createContainerFromFile('php_errors_disabled'); - $definition = $container->getDefinition('debug.debug_handlers_listener'); + $definition = $container->getDefinition('debug.error_handler_configurator'); + $this->assertNull($definition->getArgument(0)); $this->assertNull($definition->getArgument(1)); - $this->assertNull($definition->getArgument(2)); $this->assertSame(0, $container->getParameter('debug.error_handler.throw_at')); } @@ -559,21 +565,21 @@ public function testPhpErrorsWithLogLevel() { $container = $this->createContainerFromFile('php_errors_log_level'); - $definition = $container->getDefinition('debug.debug_handlers_listener'); - $this->assertEquals(new Reference('monolog.logger.php', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(1)); - $this->assertSame(8, $definition->getArgument(2)); + $definition = $container->getDefinition('debug.error_handler_configurator'); + $this->assertEquals(new Reference('monolog.logger.php', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(0)); + $this->assertSame(8, $definition->getArgument(1)); } public function testPhpErrorsWithLogLevels() { $container = $this->createContainerFromFile('php_errors_log_levels'); - $definition = $container->getDefinition('debug.debug_handlers_listener'); - $this->assertEquals(new Reference('monolog.logger.php', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(1)); + $definition = $container->getDefinition('debug.error_handler_configurator'); + $this->assertEquals(new Reference('monolog.logger.php', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(0)); $this->assertSame([ \E_NOTICE => \Psr\Log\LogLevel::ERROR, \E_WARNING => \Psr\Log\LogLevel::ERROR, - ], $definition->getArgument(2)); + ], $definition->getArgument(1)); } public function testExceptionsConfig() @@ -766,6 +772,13 @@ public function testWebLink() public function testMessengerServicesRemovedWhenDisabled() { $container = $this->createContainerFromFile('messenger_disabled'); + $messengerDefinitions = array_filter( + $container->getDefinitions(), + static fn ($name) => str_starts_with($name, 'messenger.'), + \ARRAY_FILTER_USE_KEY + ); + + $this->assertEmpty($messengerDefinitions); $this->assertFalse($container->hasDefinition('console.command.messenger_consume_messages')); $this->assertFalse($container->hasDefinition('console.command.messenger_debug')); $this->assertFalse($container->hasDefinition('console.command.messenger_stop_workers')); @@ -798,14 +811,41 @@ public function testMessengerWithExplictResetOnMessageLegacy() public function testMessenger() { - $container = $this->createContainerFromFile('messenger'); + $container = $this->createContainerFromFile('messenger', [], true, false); + $container->addCompilerPass(new ResolveTaggedIteratorArgumentPass()); + $container->compile(); + + $expectedFactories = [ + new Reference('scheduler.messenger_transport_factory'), + ]; + + if (class_exists(AmqpTransportFactory::class)) { + $expectedFactories[] = 'messenger.transport.amqp.factory'; + } + + if (class_exists(RedisTransportFactory::class)) { + $expectedFactories[] = 'messenger.transport.redis.factory'; + } + + $expectedFactories[] = 'messenger.transport.sync.factory'; + $expectedFactories[] = 'messenger.transport.in_memory.factory'; + + if (class_exists(AmazonSqsTransportFactory::class)) { + $expectedFactories[] = 'messenger.transport.sqs.factory'; + } + + if (class_exists(BeanstalkdTransportFactory::class)) { + $expectedFactories[] = 'messenger.transport.beanstalkd.factory'; + } + + $this->assertTrue($container->hasDefinition('messenger.receiver_locator')); $this->assertTrue($container->hasDefinition('console.command.messenger_consume_messages')); $this->assertTrue($container->hasAlias('messenger.default_bus')); $this->assertTrue($container->getAlias('messenger.default_bus')->isPublic()); - $this->assertTrue($container->hasDefinition('messenger.transport.amqp.factory')); - $this->assertTrue($container->hasDefinition('messenger.transport.redis.factory')); $this->assertTrue($container->hasDefinition('messenger.transport_factory')); $this->assertSame(TransportFactory::class, $container->getDefinition('messenger.transport_factory')->getClass()); + $this->assertInstanceOf(TaggedIteratorArgument::class, $container->getDefinition('messenger.transport_factory')->getArgument(0)); + $this->assertEquals($expectedFactories, $container->getDefinition('messenger.transport_factory')->getArgument(0)->getValues()); $this->assertTrue($container->hasDefinition('messenger.listener.reset_services')); $this->assertSame('messenger.listener.reset_services', (string) $container->getDefinition('console.command.messenger_consume_messages')->getArgument(5)); } @@ -822,10 +862,7 @@ public function testMessengerWithoutConsole() $this->assertFalse($container->hasDefinition('console.command.messenger_consume_messages')); $this->assertTrue($container->hasAlias('messenger.default_bus')); $this->assertTrue($container->getAlias('messenger.default_bus')->isPublic()); - $this->assertTrue($container->hasDefinition('messenger.transport.amqp.factory')); - $this->assertTrue($container->hasDefinition('messenger.transport.redis.factory')); $this->assertTrue($container->hasDefinition('messenger.transport_factory')); - $this->assertSame(TransportFactory::class, $container->getDefinition('messenger.transport_factory')->getClass()); $this->assertFalse($container->hasDefinition('messenger.listener.reset_services')); } @@ -950,6 +987,14 @@ public function testMessengerTransports() $this->assertTrue($container->hasDefinition('messenger.transport.beanstalkd.factory')); + $this->assertTrue($container->hasDefinition('messenger.transport.schedule')); + $transportFactory = $container->getDefinition('messenger.transport.schedule')->getFactory(); + $transportArguments = $container->getDefinition('messenger.transport.schedule')->getArguments(); + + $this->assertEquals([new Reference('messenger.transport_factory'), 'createTransport'], $transportFactory); + $this->assertCount(3, $transportArguments); + $this->assertSame('schedule://default', $transportArguments[0]); + $this->assertSame(10, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(0)); $this->assertSame(7, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(1)); $this->assertSame(3, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(2)); @@ -963,6 +1008,7 @@ public function testMessengerTransports() 'default' => new Reference('messenger.transport.failed'), 'failed' => new Reference('messenger.transport.failed'), 'redis' => new Reference('messenger.transport.failed'), + 'schedule' => new Reference('messenger.transport.failed'), ]; $failureTransportsReferences = array_map(function (ServiceClosureArgument $serviceClosureArgument) { @@ -1056,6 +1102,13 @@ public function testMessengerMiddlewareFactoryErroneousFormat() } public function testMessengerInvalidTransportRouting() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Invalid Messenger routing configuration: invalid namespace "Symfony\*\DummyMessage" wildcard.'); + $this->createContainerFromFile('messenger_routing_invalid_wildcard'); + } + + public function testMessengerInvalidWildcardRouting() { $this->expectException(\LogicException::class); $this->expectExceptionMessage('Invalid Messenger routing configuration: the "Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage" class is being routed to a sender called "invalid". This is not a valid transport or service id.'); @@ -1136,9 +1189,7 @@ public function testTranslator() $nonExistingDirectories = array_filter( $options['scanned_directories'], - function ($directory) { - return !file_exists($directory); - } + fn ($directory) => !file_exists($directory) ); $this->assertNotEmpty($nonExistingDirectories, 'FrameworkBundle should pass non existing directories to Translator'); @@ -1408,7 +1459,7 @@ public function testSerializerEnabled() $argument = $container->getDefinition('serializer.mapping.chain_loader')->getArgument(0); $this->assertCount(2, $argument); - $this->assertEquals('Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', $argument[0]->getClass()); + $this->assertEquals(AnnotationLoader::class, $argument[0]->getClass()); $this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1)); $this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3)); $this->assertArrayHasKey('circular_reference_handler', $container->getDefinition('serializer.normalizer.object')->getArgument(6)); @@ -1490,7 +1541,7 @@ public function testObjectNormalizerRegistered() $definition = $container->getDefinition('serializer.normalizer.object'); $tag = $definition->getTag('serializer.normalizer'); - $this->assertEquals('Symfony\Component\Serializer\Normalizer\ObjectNormalizer', $definition->getClass()); + $this->assertEquals(ObjectNormalizer::class, $definition->getClass()); $this->assertEquals(-1000, $tag[0]['priority']); } @@ -1843,13 +1894,14 @@ public function testRobotsTagListenerIsRegisteredInDebugMode() public function testHttpClientDefaultOptions() { $container = $this->createContainerFromFile('http_client_default_options'); - $this->assertTrue($container->hasDefinition('http_client'), '->registerHttpClientConfiguration() loads http_client.xml'); + $this->assertTrue($container->hasDefinition('http_client.transport'), '->registerHttpClientConfiguration() loads http_client.xml'); $defaultOptions = [ 'headers' => [], 'resolve' => [], + 'extra' => [], ]; - $this->assertSame([$defaultOptions, 4], $container->getDefinition('http_client')->getArguments()); + $this->assertSame([$defaultOptions, 4], $container->getDefinition('http_client.transport')->getArguments()); $this->assertTrue($container->hasDefinition('foo'), 'should have the "foo" service.'); $this->assertSame(ScopingHttpClient::class, $container->getDefinition('foo')->getClass()); @@ -1867,18 +1919,22 @@ public function testHttpClientOverrideDefaultOptions() { $container = $this->createContainerFromFile('http_client_over 10000 ride_default_options'); - $this->assertSame(['foo' => 'bar'], $container->getDefinition('http_client')->getArgument(0)['headers']); - $this->assertSame(4, $container->getDefinition('http_client')->getArgument(1)); + $this->assertSame(['foo' => 'bar'], $container->getDefinition('http_client.transport')->getArgument(0)['headers']); + $this->assertSame(['foo' => 'bar'], $container->getDefinition('http_client.transport')->getArgument(0)['extra']); + $this->assertSame(4, $container->getDefinition('http_client.transport')->getArgument(1)); $this->assertSame('http://example.com', $container->getDefinition('foo')->getArgument(1)); $expected = [ 'headers' => [ 'bar' => 'baz', ], + 'extra' => [ + 'bar' => 'baz', + ], 'query' => [], 'resolve' => [], ]; - $this->assertSame($expected, $container->getDefinition('foo')->getArgument(2)); + $this->assertEquals($expected, $container->getDefinition('foo')->getArgument(2)); } public function testHttpClientRetry() @@ -1918,7 +1974,7 @@ public function testHttpClientFullDefaultOptions() { $container = $this->createContainerFromFile('http_client_full_default_options'); - $defaultOptions = $container->getDefinition('http_client')->getArgument(0); + $defaultOptions = $container->getDefinition('http_client.transport')->getArgument(0); $this->assertSame(['X-powered' => 'PHP'], $defaultOptions['headers']); $this->assertSame(2, $defaultOptions['max_redirects']); @@ -1940,6 +1996,7 @@ public function testHttpClientFullDefaultOptions() 'pin-sha256' => ['14s5erg62v1v8471g2revg48r7==', 'jsda84hjtyd4821bgfesd215bsfg5412='], 'md5' => 'sdhtb481248721thbr=', ], $defaultOptions['peer_fingerprint']); + $this->assertSame(['foo' => ['bar' => 'baz']], $defaultOptions['extra']); } public static function provideMailer(): array @@ -2003,7 +2060,7 @@ public function testHttpClientMockResponseFactory() $argument = $definition->getArgument(0); $this->assertInstanceOf(Reference::class, $argument); - $this->assertSame('http_client', current($definition->getDecoratedService())); + $this->assertSame('http_client.transport', current($definition->getDecoratedService())); $this->assertSame('my_response_factory', (string) $argument); } @@ -2197,10 +2254,32 @@ public function testHtmlSanitizerDefaultConfig() $this->assertSame('html_sanitizer', (string) $container->getAlias(HtmlSanitizerInterface::class)); } + public function testNotifierWithDisabledMessageBus() + { + $container = $this->createContainerFromFile('notifier_with_disabled_message_bus'); + + $this->assertNull($container->getDefinition('chatter')->getArgument(1)); + $this->assertNull($container->getDefinition('texter')->getArgument(1)); + $this->assertNull($container->getDefinition('notifier.channel.chat')->getArgument(1)); + $this->assertNull($container->getDefinition('notifier.channel.email')->getArgument(1)); + $this->assertNull($container->getDefinition('notifier.channel.sms')->getArgument(1)); + } + + public function testNotifierWithSpecificMessageBus() + { + $container = $this->createContainerFromFile('notifier_with_specific_message_bus'); + + $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('chatter')->getArgument(1)); + $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('texter')->getArgument(1)); + $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('notifier.channel.chat')->getArgument(1)); + $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('notifier.channel.email')->getArgument(1)); + $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('notifier.channel.sms')->getArgument(1)); + } + protected function createContainer(array $data = []) { return new ContainerBuilder(new EnvPlaceholderParameterBag(array_merge([ - 'kernel.bundles' => ['FrameworkBundle' => 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle'], + 'kernel.bundles' => ['FrameworkBundle' => FrameworkBundle::class], 'kernel.bundles_metadata' => ['FrameworkBundle' => ['namespace' => 'Symfony\\Bundle\\FrameworkBundle', 'path' => __DIR__.'/../..']], 'kernel.cache_dir' => __DIR__, 'kernel.build_dir' => __DIR__, @@ -2320,7 +2399,7 @@ private function assertCachePoolServiceDefinitionIsCreated(ContainerBuilder $con */ class TestAnnotationsPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { $container->setDefinition('annotation_reader', $container->getDefinition('annotations.cached_reader')); $container->removeDefinition('annotations.cached_reader'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php index 4a2ff788bf5c6..6b08cc19f712a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php @@ -14,7 +14,6 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; -use Symfony\Component\RateLimiter\Policy\SlidingWindowLimiter; class XmlFrameworkExtensionTest extends FrameworkExtensionTestCase { @@ -74,4 +73,19 @@ public function testRateLimiter() $this->assertTrue($container->hasDefinition('limiter.sliding_window')); } + + public function testAssetMapper() + { + $container = $this->createContainerFromFile('asset_mapper'); + + $definition = $container->getDefinition('asset_mapper'); + $this->assertSame('/assets_path/', $definition->getArgument(3)); + $this->assertSame(['zip' => 'application/zip'], $definition->getArgument(5)); + + $definition = $container->getDefinition('asset_mapper.importmap.renderer'); + $this->assertSame(['data-turbo-track' => 'reload'], $definition->getArgument(3)); + + $definition = $container->getDefinition('asset_mapper.repository'); + $this->assertSame(['assets/' => '', 'assets2/' => 'my_namespace'], $definition->getArgument(0)); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Messenger/DummySchedule.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Messenger/DummySchedule.php new file mode 100644 index 0000000000000..51a50e33ca796 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Messenger/DummySchedule.php @@ -0,0 +1,26 @@ +add(...self::$recurringMessages) + ->stateful(new ArrayAdapter()) + ->lock(new Lock(new Key('dummy'), new InMemoryStore())) + ; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php index bce53b8668251..085cb812eba69 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\app\AppKernel; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\KernelInterface; @@ -47,7 +48,7 @@ protected static function getKernelClass(): string { require_once __DIR__.'/app/AppKernel.php'; - return 'Symfony\Bundle\FrameworkBundle\Tests\Functional\app\AppKernel'; + return AppKernel::class; } protected static function createKernel(array $options = []): KernelInterface diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ApiAttributesTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ApiAttributesTest.php new file mode 100644 index 0000000000000..96b6d0ee98e14 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ApiAttributesTest.php @@ -0,0 +1,419 @@ + + * + * 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\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\MapQueryString; +use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; +use Symfony\Component\Validator\Constraints as Assert; + +class ApiAttributesTest extends AbstractWebTestCase +{ + /** + * @dataProvider mapQueryStringProvider + */ + public function testMapQueryString(array $query, string $expectedResponse, int $expectedStatusCode) + { + $client = self::createClient(['test_case' => 'ApiAttributesTest']); + + $client->request('GET', '/map-query-string.json', $query); + + $response = $client->getResponse(); + if ($expectedResponse) { + self::assertJsonStringEqualsJsonString($expectedResponse, $response->getContent()); + } else { + self::assertEmpty($response->getContent()); + } + self::assertSame($expectedStatusCode, $response->getStatusCode()); + } + + public static function mapQueryStringProvider(): iterable + { + yield 'empty' => [ + 'query' => [], + 'expectedResponse' => '', + 'expectedStatusCode' => 204, + ]; + + yield 'valid' => [ + 'query' => ['filter' => ['status' => 'approved', 'quantity' => '4']], + 'expectedResponse' => <<<'JSON' + { + "filter": { + "status": "approved", + "quantity": 4 + } + } + JSON, + 'expectedStatusCode' => 200, + ]; + + yield 'invalid' => [ + 'query' => ['filter' => ['status' => 'approved', 'quantity' => '200']], + 'expectedResponse' => <<<'JSON' + { + "type": "https:\/\/symfony.com\/errors\/validation", + "title": "Validation Failed", + "status": 404, + "detail": "filter.quantity: This value should be less than 10.", + "violations": [ + { + "propertyPath": "filter.quantity", + "title": "This value should be less than 10.", + "template": "This value should be less than {{ compared_value }}.", + "parameters": { + "{{ value }}": "200", + "{{ compared_value }}": "10", + "{{ compared_value_type }}": "int" + }, + "type": "urn:uuid:079d7420-2d13-460c-8756-de810eeb37d2" + } + ] + } + JSON, + 'expectedStatusCode' => 404, + ]; + } + + /** + * @dataProvider mapRequestPayloadProvider + */ + public function testMapRequestPayload(string $format, array $parameters, ?string $content, string $expectedResponse, int $expectedStatusCode) + { + $client = self::createClient(['test_case' => 'ApiAttributesTest']); + + [$acceptHeader, $assertion] = [ + 'html' => ['text/html', self::assertStringContainsString(...)], + 'json' => ['application/json', self::assertJsonStringEqualsJsonString(...)], + 'xml' => ['text/xml', self::assertXmlStringEqualsXmlString(...)], + 'dummy' => ['application/dummy', self::assertStringContainsString(...)], + ][$format]; + + $client->request( + 'POST', + '/map-request-body.'.$format, + $parameters, + [], + ['HTTP_ACCEPT' => $acceptHeader, 'CONTENT_TYPE' => $acceptHeader], + $content + ); + + $response = $client->getResponse(); + $responseContent = $response->getContent(); + + if ($expectedResponse) { + $assertion($expectedResponse, $responseContent); + } else { + self::assertSame('', $responseContent); + } + + self::assertSame($expectedStatusCode, $response->getStatusCode()); + } + + public static function mapRequestPayloadProvider(): iterable + { + yield 'empty' => [ + 'format' => 'json', + 'parameters' => [], + 'content' => '', + 'expectedResponse' => '', + 'expectedStatusCode' => 204, + ]; + + yield 'valid json' => [ + 'format' => 'json', + 'parameters' => [], + 'content' => <<<'JSON' + { + "comment": "Hello everyone!", + "approved": false + } + JSON, + 'expectedResponse' => <<<'JSON' + { + "comment": "Hello everyone!", + "approved": false + } + JSON, + 'expectedStatusCode' => 200, + ]; + + yield 'malformed json' => [ + 'format' => 'json', + 'parameters' => [], + 'content' => <<<'JSON' + { + "comment": "Hello everyone!", + "approved": false, + } + JSON, + 'expectedResponse' => <<<'JSON' + { + "type": "https:\/\/tools.ietf.org\/html\/rfc2616#section-10", + "title": "An error occurred", + "status": 400, + "detail": "Bad Request" + } + JSON, + 'expectedStatusCode' => 400, + ]; + + yield 'unsupported format' => [ + 'format' => 'dummy', + 'parameters' => [], + 'content' => 'Hello', + 'expectedResponse' => '415 Unsupported Media Type', + 'expectedStatusCode' => 415, + ]; + + yield 'valid xml' => [ + 'format' => 'xml', + 'parameters' => [], + 'content' => <<<'XML' + + Hello everyone! + true + + XML, + 'expectedResponse' => <<<'XML' + + Hello everyone! + 1 + + XML, + 'expectedStatusCode' => 200, + ]; + + yield 'invalid type' => [ + 'format' => 'json', + 'parameters' => [], + 'content' => <<<'JSON' + { + "comment": "Hello everyone!", + "approved": "string instead of bool" + } + JSON, + 'expectedResponse' => <<<'JSON' + { + "type": "https:\/\/symfony.com\/errors\/validation", + "title": "Validation Failed", + "status": 422, + "detail": "approved: This value should be of type bool.", + "violations": [ + { + "propertyPath": "approved", + "title": "This value should be of type bool.", + "template": "This value should be of type {{ type }}.", + "parameters": { + "{{ type }}": "bool" + } + } + ] + } + JSON, + 'expectedStatusCode' => 422, + ]; + + yield 'validation error json' => [ + 'format' => 'json', + 'parameters' => [], + 'content' => <<<'JSON' + { + "comment": "", + "approved": true + } + JSON, + 'expectedResponse' => <<<'JSON' + { + "type": "https:\/\/symfony.com\/errors\/validation", + "title": "Validation Failed", + "status": 422, + "detail": "comment: This value should not be blank.\ncomment: This value is too short. It should have 10 characters or more.", + "violations": [ + { + "propertyPath": "comment", + "title": "This value should not be blank.", + "template": "This value should not be blank.", + "parameters": { + "{{ value }}": "\"\"" + }, + "type": "urn:uuid:c1051bb4-d103-4f74-8988-acbcafc7fdc3" + }, + { + "propertyPath": "comment", + "title": "This value is too short. It should have 10 characters or more.", + "template": "This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.", + "parameters": { + "{{ value }}": "\"\"", + "{{ limit }}": "10", + "{{ value_length }}": "0" + }, + "type": "urn:uuid:9ff3fdc4-b214-49db-8718-39c315e33d45" + } + ] + } + JSON, + 'expectedStatusCode' => 422, + ]; + + yield 'validation error xml' => [ + 'format' => 'xml', + 'parameters' => [], + 'content' => <<<'XML' + + H + false + + XML, + 'expectedResponse' => <<<'XML' + + + https://symfony.com/errors/validation + Validation Failed + 422 + comment: This value is too short. It should have 10 characters or more. + + comment + This value is too short. It should have 10 characters or more. + + + "H" + 10 + 1 + + urn:uuid:9ff3fdc4-b214-49db-8718-39c315e33d45 + + + XML, + 'expectedStatusCode' => 422, + ]; + + yield 'valid input' => [ + 'format' => 'json', + 'input' => ['comment' => 'Hello everyone!', 'approved' => '0'], + 'content' => null, + 'expectedResponse' => <<<'JSON' + { + "comment": "Hello everyone!", + "approved": false + } + JSON, + 'expectedStatusCode' => 200, + ]; + + yield 'validation error input' => [ + 'format' => 'json', + 'input' => ['comment' => '', 'approved' => '1'], + 'content' => null, + 'expectedResponse' => <<<'JSON' + { + "type": "https:\/\/symfony.com\/errors\/validation", + "title": "Validation Failed", + "status": 422, + "detail": "comment: This value should not be blank.\ncomment: This value is too short. It should have 10 characters or more.", + "violations": [ + { + "propertyPath": "comment", + "title": "This value should not be blank.", + "template": "This value should not be blank.", + "parameters": { + "{{ value }}": "\"\"" + }, + "type": "urn:uuid:c1051bb4-d103-4f74-8988-acbcafc7fdc3" + }, + { + "propertyPath": "comment", + "title": "This value is too short. It should have 10 characters or more.", + "template": "This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.", + "parameters": { + "{{ value }}": "\"\"", + "{{ limit }}": "10", + "{{ value_length }}": "0" + }, + "type": "urn:uuid:9ff3fdc4-b214-49db-8718-39c315e33d45" + } + ] + } + JSON, + 'expectedStatusCode' => 422, + ]; + } +} + +class WithMapQueryStringController +{ + public function __invoke(#[MapQueryString] ?QueryString $query): Response + { + if (!$query) { + return new Response('', Response::HTTP_NO_CONTENT); + } + + return new JsonResponse( + ['filter' => ['status' => $query->filter->status, 'quantity' => $query->filter->quantity]], + ); + } +} + +class WithMapRequestPayloadController +{ + public function __invoke(#[MapRequestPayload] ?RequestBody $body, Request $request): Response + { + if ('json' === $request->getPreferredFormat('json')) { + if (!$body) { + return new Response('', Response::HTTP_NO_CONTENT); + } + + return new JsonResponse(['comment' => $body->comment, 'approved' => $body->approved]); + } + + return new Response( + << + {$body->comment} + {$body->approved} + + XML + ); + } +} + +class QueryString +{ + public function __construct( + #[Assert\Valid] + public readonly Filter $filter, + ) { + } +} + +class Filter +{ + public function __construct( + public readonly string $status, + #[Assert\LessThan(10)] + public readonly int $quantity, + ) { + } +} + +class RequestBody +{ + public function __construct( + #[Assert\NotBlank] + #[Assert\Length(min: 10)] + public readonly string $comment, + public readonly bool $approved, + ) { + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php index 400384f616f29..67d7b2bdac997 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php @@ -16,7 +16,7 @@ class DefaultConfigTestExtension extends Extension { - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php index 497a7a99d6d2b..560f84e62df4f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php @@ -16,7 +16,7 @@ class ExtensionWithoutConfigTestExtension implements ExtensionInterface { - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/AnnotationReaderPass.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/AnnotationReaderPass.php index 53555fd664174..9e61c5ae76f64 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/AnnotationReaderPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/AnnotationReaderPass.php @@ -16,7 +16,7 @@ class AnnotationReaderPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { // simulate using "annotation_reader" in a compiler pass $container->get('test.annotation_reader'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TestExtension.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TestExtension.php index 80cee0f1ae344..b0cd9ff916816 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TestExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TestExtension.php @@ -21,7 +21,7 @@ class TestExtension extends Extension implements PrependExtensionInterface { private $customConfig; - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $configuration = $this->getConfiguration($configs, $container); $this->processConfiguration($configuration, $configs); @@ -29,7 +29,7 @@ public function load(array $configs, ContainerBuilder $container) $container->setAlias('test.annotation_reader', new Alias('annotation_reader', true)); } - public function prepend(ContainerBuilder $container) + public function prepend(ContainerBuilder $container): void { $container->prependExtensionConfig('test', ['custom' => 'foo']); } @@ -39,7 +39,7 @@ public function getConfiguration(array $config, ContainerBuilder $container): ?C return new Configuration($this->customConfig); } - public function setCustomConfig($customConfig) + public function setCustomConfig($customConfig): void { $this->customConfig = $customConfig; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php index 13c368fd585e4..bde1279ad1ec3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php @@ -21,7 +21,7 @@ class TestBundle extends Bundle { - public function build(ContainerBuilder $container) + public function build(ContainerBuilder $container): void { parent::build($container); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php index b01295607b17f..76ac645d2b6f6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php @@ -14,6 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\Finder\SplFileInfo; @@ -76,6 +77,33 @@ public function testClearUnexistingPool() ->execute(['pools' => ['unknown_pool']], ['decorated' => false]); } + public function testClearAll() + { + $tester = $this->createCommandTester(['cache.app_clearer']); + $tester->execute(['--all' => true], ['decorated' => false]); + + $tester->assertCommandIsSuccessful('cache:pool:clear exits with 0 in case of success'); + $this->assertStringContainsString('Clearing all cache pools...', $tester->getDisplay()); + $this->assertStringContainsString('Calling cache clearer: cache.app_clearer', $tester->getDisplay()); + $this->assertStringContainsString('[OK] Cache was successfully cleared.', $tester->getDisplay()); + } + + public function testClearWithoutPoolNames() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Could not clear all cache pools, try specifying a specific pool or cache clearer.'); + + $this->createCommandTester()->execute(['--all' => true], ['decorated' => false]); + } + + public function testClearNoOptions() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Either specify at least one pool name, or provide the --all option to clear all pools.'); + + $this->createCommandTester()->execute([], ['decorated' => false]); + } + public function testClearFailed() { $tester = $this->createCommandTester(); @@ -104,10 +132,10 @@ public function testClearFailed() $this->assertStringContainsString('[WARNING] Cache pool "cache.public_pool" could not be cleared.', $tester->getDisplay()); } - private function createCommandTester() + private function createCommandTester(array $poolNames = null) { $application = new Application(static::$kernel); - $application->add(new CachePoolClearCommand(static::getContainer()->get('cache.global_clearer'))); + $application->add(new CachePoolClearCommand(static::getContainer()->get('cache.global_clearer'), $poolNames)); return new CommandTester($application->find('cache:pool:clear')); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php index 60383509b8ec6..307b34a395299 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Tester\CommandCompletionTester; @@ -50,6 +51,19 @@ public function testDumpBundleOption() $this->assertStringContainsString('foo', $tester->getDisplay()); } + public function testDumpWithUnsupportedFormat() + { + $tester = $this->createCommandTester(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Supported formats are "yaml", "json"'); + + $tester->execute([ + 'name' => 'test', + '--format' => 'xml', + ]); + } + public function testParametersValuesAreResolved() { $tester = $this->createCommandTester(); @@ -157,6 +171,8 @@ public static function provideCompletionSuggestions(): \Generator yield 'name (started CamelCase)' => [['Fra'], ['DefaultConfigTestBundle', 'ExtensionWithoutConfigTestBundle', 'FrameworkBundle', 'TestBundle']]; yield 'name with existing path' => [['framework', ''], ['secret', 'router.resource', 'router.utf8', 'router.enabled', 'validation.enabled', 'default_locale']]; + + yield 'option --format' => [['--format', ''], ['yaml', 'json']]; } private function createCommandTester(): CommandTester diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php index 67727202e72b7..b3624cc5c9e74 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php @@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\BackslashClass; use Symfony\Component\Console\Tester\ApplicationTester; use Symfony\Component\Console\Tester\CommandCompletionTester; +use Symfony\Component\HttpKernel\HttpKernelInterface; /** * @group functional @@ -49,6 +50,19 @@ public function testNoDebug() $this->assertStringContainsString('public', $tester->getDisplay()); } + public function testNoDumpedXML() + { + static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml', 'debug' => true, 'debug.container.dump' => false]); + + $application = new Application(static::$kernel); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(['command' => 'debug:container']); + + $this->assertStringContainsString('public', $tester->getDisplay()); + } + public function testPrivateAlias() { static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml']); @@ -273,7 +287,7 @@ public static function provideCompletionSuggestions() { $serviceId = 'console.command.container_debug'; $hiddenServiceId = '.console.command.container_debug.lazy'; - $interfaceServiceId = 'Symfony\Component\HttpKernel\HttpKernelInterface'; + $interfaceServiceId = HttpKernelInterface::class; yield 'name' => [ [''], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php index 345589c1b6fc2..db2ee9c468117 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php @@ -11,10 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ClassAliasExampleClass; use Symfony\Component\Console\Tester\ApplicationTester; use Symfony\Component\Console\Tester\CommandCompletionTester; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Routing\RouterInterface; /** * @group functional @@ -31,7 +35,7 @@ public function testBasicFunctionality() $tester = new ApplicationTester($application); $tester->run(['command' => 'debug:autowiring']); - $this->assertStringContainsString('Symfony\Component\HttpKernel\HttpKernelInterface', $tester->getDisplay()); + $this->assertStringContainsString(HttpKernelInterface::class, $tester->getDisplay()); $this->assertStringContainsString('(http_kernel)', $tester->getDisplay()); } @@ -45,8 +49,8 @@ public function testSearchArgument() $tester = new ApplicationTester($application); $tester->run(['command' => 'debug:autowiring', 'search' => 'kern']); - $this->assertStringContainsString('Symfony\Component\HttpKernel\HttpKernelInterface', $tester->getDisplay()); - $this->assertStringNotContainsString('Symfony\Component\Routing\RouterInterface', $tester->getDisplay()); + $this->assertStringContainsString(HttpKernelInterface::class, $tester->getDisplay()); + $this->assertStringNotContainsString(RouterInterface::class, $tester->getDisplay()); } public function testSearchIgnoreBackslashWhenFindingService() @@ -58,7 +62,7 @@ public function testSearchIgnoreBackslashWhenFindingService() $tester = new ApplicationTester($application); $tester->run(['command' => 'debug:autowiring', 'search' => 'HttpKernelHttpKernelInterface']); - $this->assertStringContainsString('Symfony\Component\HttpKernel\HttpKernelInterface', $tester->getDisplay()); + $this->assertStringContainsString(HttpKernelInterface::class, $tester->getDisplay()); } public function testSearchNoResults() @@ -109,7 +113,7 @@ public function testNotConfusedByClassAliases() $tester = new ApplicationTester($application); $tester->run(['command' => 'debug:autowiring', 'search' => 'ClassAlias']); - $this->assertStringContainsString('Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ClassAliasExampleClass', $tester->getDisplay()); + $this->assertStringContainsString(ClassAliasExampleClass::class, $tester->getDisplay()); } /** @@ -131,6 +135,6 @@ public function testComplete(array $input, array $expectedSuggestions) public static function provideCompletionSuggestions(): \Generator { - yield 'search' => [[''], ['SessionHandlerInterface', 'Psr\\Log\\LoggerInterface', 'Psr\\Container\\ContainerInterface $parameterBag']]; + yield 'search' => [[''], ['SessionHandlerInterface', LoggerInterface::class, 'Psr\\Container\\ContainerInterface $parameterBag']]; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php index 32bee3b587309..aa12467829ed1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php @@ -17,6 +17,7 @@ use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PublicService; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\UnusedPrivateService; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; class KernelTestCaseTest extends AbstractWebTestCase { @@ -41,4 +42,20 @@ public function testThatPrivateServicesAreAvailableIfTestConfigIsEnabled() $this->assertTrue($container->has('private_service')); $this->assertFalse($container->has(UnusedPrivateService::class)); } + + public function testThatPrivateServicesCanBeSetIfTestConfigIsEnabled() + { + static::bootKernel(['test_case' => 'TestServiceContainer']); + + $container = static::getContainer(); + + $service = new \stdClass(); + + $container->set('private_service', $service); + $this->assertSame($service, $container->get('private_service')); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The "private_service" service is already initialized, you cannot replace it.'); + $container->set('private_service', new \stdClass()); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SchedulerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SchedulerTest.php new file mode 100644 index 0000000000000..5aef74f473088 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SchedulerTest.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\FrameworkBundle\Tests\Functional; + +use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\BarMessage; +use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummySchedule; +use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\FooMessage; +use Symfony\Component\Clock\MockClock; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Scheduler\Messenger\SchedulerTransport; +use Symfony\Component\Scheduler\RecurringMessage; + +class SchedulerTest extends AbstractWebTestCase +{ + public function testScheduler() + { + $scheduledMessages = [ + RecurringMessage::every('5 minutes', $foo = new FooMessage(), new \DateTimeImmutable('2020-01-01T00:00:00Z')), + RecurringMessage::every('5 minutes', $bar = new BarMessage(), new \DateTimeImmutable('2020-01-01T00:01:00Z')), + ]; + DummySchedule::$recurringMessages = $scheduledMessages; + + $container = self::getContainer(); + $container->set('clock', $clock = new MockClock('2020-01-01T00:09:59Z')); + + $this->assertTrue($container->get('receivers')->has('scheduler_dummy')); + $this->assertInstanceOf(SchedulerTransport::class, $cron = $container->get('receivers')->get('scheduler_dummy')); + + $fetchMessages = static function (float $sleep) use ($clock, $cron) { + if (0 < $sleep) { + $clock->sleep($sleep); + } + $messages = []; + foreach ($cron->get() as $key => $envelope) { + $messages[$key] = $envelope->getMessage(); + } + + return $messages; + }; + + $this->assertSame([], $fetchMessages(0.0)); + $this->assertSame([$foo], $fetchMessages(1.0)); + $this->assertSame([], $fetchMessages(1.0)); + $this->assertSame([$bar], $fetchMessages(60.0)); + $this->assertSame([$foo, $bar, $foo, $bar], $fetchMessages(600.0)); + } + + protected static function createKernel(array $options = []): KernelInterface + { + return parent::createKernel(['test_case' => 'Scheduler'] + $options); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SluggerLocaleAwareTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SluggerLocaleAwareTest.php index d8552977c0835..76901246138b6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SluggerLocaleAwareTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SluggerLocaleAwareTest.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Slugger\SlugConstructArgService; + /** * @group functional */ @@ -24,7 +26,7 @@ public function testLocalizedSlugger() $kernel = static::createKernel(['test_case' => 'Slugger', 'root_config' => 'config.yml']); $kernel->boot(); - $service = $kernel->getContainer()->get('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Slugger\SlugConstructArgService'); + $service = $kernel->getContainer()->get(SlugConstructArgService::class); $this->assertSame('Stoinostta-tryabva-da-bude-luzha', $service->hello()); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ApiAttributesTest/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ApiAttributesTest/bundles.php new file mode 100644 index 0000000000000..13ab9fddee4a6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ApiAttributesTest/bundles.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; + +return [ + new FrameworkBundle(), +]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ApiAttributesTest/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ApiAttributesTest/config.yml new file mode 100644 index 0000000000000..8b218d48cbb06 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ApiAttributesTest/config.yml @@ -0,0 +1,8 @@ +imports: + - { resource: ../config/default.yml } + +framework: + serializer: + enabled: true + validation: true + property_info: { enabled: true } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ApiAttributesTest/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ApiAttributesTest/routing.yml new file mode 100644 index 0000000000000..9ec40e1708c2b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ApiAttributesTest/routing.yml @@ -0,0 +1,7 @@ +map_query_string: + path: /map-query-string.{_format} + controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\WithMapQueryStringController + +map_request_body: + path: /map-request-body.{_format} + controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\WithMapRequestPayloadController diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php index b182e902d7392..3f99eff48d57c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php @@ -73,12 +73,12 @@ public function getLogDir(): string return sys_get_temp_dir().'/'.$this->varDir.'/'.$this->testCase.'/logs'; } - public function registerContainerConfiguration(LoaderInterface $loader) + public function registerContainerConfiguration(LoaderInterface $loader): void { $loader->load($this->rootConfig); } - protected function build(ContainerBuilder $container) + protected function build(ContainerBuilder $container): void { $container->register('logger', NullLogger::class); $container->registerExtension(new TestDumpExtension()); @@ -117,7 +117,7 @@ public function getConfigTreeBuilder(): TreeBuilder return $treeBuilder; } - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Scheduler/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Scheduler/bundles.php new file mode 100644 index 0000000000000..13ab9fddee4a6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Scheduler/bundles.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; + +return [ + new FrameworkBundle(), +]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Scheduler/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Scheduler/config.yml new file mode 100644 index 0000000000000..e39d423f4f4cd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Scheduler/config.yml @@ -0,0 +1,18 @@ +imports: + - { resource: ../config/default.yml } + +framework: + lock: ~ + scheduler: ~ + messenger: ~ + +services: + Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummySchedule: + autoconfigure: true + + clock: + synthetic: true + + receivers: + public: true + alias: 'messenger.receiver_locator' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php index 2cebebed14df0..d89a09489baa3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php @@ -647,9 +647,7 @@ private function getParameterBag(array $params = []): ContainerInterface $bag ->expects($this->any()) ->method('get') - ->willReturnCallback(function ($key) use ($params) { - return $params[$key] ?? null; - }) + ->willReturnCallback(fn ($key) => $params[$key] ?? null) ; return $bag; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php index 41c9d1bf77fa3..802f676070519 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php @@ -190,6 +190,16 @@ public function testAssertSelectorNotExists() $this->getCrawlerTester(new Crawler('

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

Hello

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

Hello

Foo

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

This is not a paragraph.

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

Hello

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

Foo'))->assertSelectorTextNotContains('body > h1', 'Bar'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php index 3116080f042c0..dac3b6394fce8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php @@ -118,6 +118,9 @@ public function warmUp(string $cacheDir): array return []; } + /** + * @return void + */ public function addResource(string $format, mixed $resource, string $locale, string $domain = null) { if ($this->resourceFiles) { @@ -126,6 +129,9 @@ public function addResource(string $format, mixed $resource, string $locale, str $this->resources[] = [$format, $resource, $locale, $domain]; } + /** + * @return void + */ protected function initializeCatalogue(string $locale) { $this->initialize(); @@ -145,6 +151,9 @@ protected function doLoadCatalogue(string $locale): void } } + /** + * @return void + */ protected function initialize() { if ($this->resourceFiles) { diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 4fc22ab7122bd..eb3d340a71b96 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -22,11 +22,11 @@ "symfony/cache": "^5.4|^6.0", "symfony/config": "^6.1", "symfony/dependency-injection": "^6.2.8", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.1", "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-foundation": "^6.2", - "symfony/http-kernel": "^6.2.1", + "symfony/http-foundation": "^6.3", + "symfony/http-kernel": "^6.3", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", @@ -36,31 +36,34 @@ "doctrine/annotations": "^1.13.1|^2", "doctrine/persistence": "^1.3|^2|^3", "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", "symfony/browser-kit": "^5.4|^6.0", "symfony/console": "^5.4.9|^6.0.9", + "symfony/clock": "^6.2", "symfony/css-selector": "^5.4|^6.0", - "symfony/dom-crawler": "^5.4|^6.0", + "symfony/dom-crawler": "^6.3", "symfony/dotenv": "^5.4|^6.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/form": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/html-sanitizer": "^6.1", - "symfony/http-client": "^5.4|^6.0", + "symfony/http-client": "^6.3", "symfony/lock": "^5.4|^6.0", "symfony/mailer": "^5.4|^6.0", - "symfony/messenger": "^6.2", + "symfony/messenger": "^6.3", "symfony/mime": "^6.2", "symfony/notifier": "^5.4|^6.0", "symfony/process": "^5.4|^6.0", "symfony/rate-limiter": "^5.4|^6.0", + "symfony/scheduler": "^6.3", "symfony/security-bundle": "^5.4|^6.0", "symfony/semaphore": "^5.4|^6.0", - "symfony/serializer": "^6.1", + "symfony/serializer": "^6.3", "symfony/stopwatch": "^5.4|^6.0", "symfony/string": "^5.4|^6.0", "symfony/translation": "^6.2.8", "symfony/twig-bundle": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", + "symfony/validator": "^6.3", "symfony/workflow": "^5.4|^6.0", "symfony/yaml": "^5.4|^6.0", "symfony/property-info": "^5.4|^6.0", @@ -76,38 +79,29 @@ "phpdocumentor/type-resolver": "<1.4.0", "phpunit/phpunit": "<5.4.3", "symfony/asset": "<5.4", + "symfony/clock": "<6.3", "symfony/console": "<5.4", "symfony/dotenv": "<5.4", - "symfony/dom-crawler": "<5.4", - "symfony/http-client": "<5.4", + "symfony/dom-crawler": "<6.3", + "symfony/http-client": "<6.3", "symfony/form": "<5.4", "symfony/lock": "<5.4", "symfony/mailer": "<5.4", - "symfony/messenger": "<6.2", + "symfony/messenger": "<6.3", "symfony/mime": "<6.2", "symfony/property-info": "<5.4", "symfony/property-access": "<5.4", - "symfony/serializer": "<6.1", + "symfony/serializer": "<6.3", "symfony/security-csrf": "<5.4", "symfony/security-core": "<5.4", "symfony/stopwatch": "<5.4", "symfony/translation": "<6.2.8", "symfony/twig-bridge": "<5.4", "symfony/twig-bundle": "<5.4", - "symfony/validator": "<5.4", + "symfony/validator": "<6.3", "symfony/web-profiler-bundle": "<5.4", "symfony/workflow": "<5.4" }, - "suggest": { - "ext-apcu": "For best performance of the system caches", - "symfony/console": "For using the console commands", - "symfony/form": "For using forms", - "symfony/serializer": "For using the serializer service", - "symfony/validator": "For using validation", - "symfony/yaml": "For using the debug:config and lint:yaml commands", - "symfony/property-info": "For using the property_info service", - "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering" - }, "autoload": { "psr-4": { "Symfony\\Bundle\\FrameworkBundle\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index dcefe374dda4c..99b9b94ea3db8 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,18 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate enabling bundle and not configuring it + * Add `_stateless` attribute to the request when firewall is stateless and the attribute is not already set + * Add `StatelessAuthenticatorFactoryInterface` for authenticators targeting `stateless` firewalls only and that don't require a user provider + * Modify "icon.svg" to improve accessibility for blind/low vision users + * Make `Security::login()` return the authenticator response + * Deprecate the `security.firewalls.logout.csrf_token_generator` config option, use `security.firewalls.logout.csrf_token_manager` instead + * Make firewalls event dispatcher traceable on debug mode + * Add `TokenHandlerFactoryInterface`, `OidcUserInfoTokenHandlerFactory`, `OidcTokenHandlerFactory` and `ServiceTokenHandlerFactory` for `AccessTokenFactory` + 6.2 --- diff --git a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php index 8a87422862287..846d82dec0710 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php @@ -152,7 +152,7 @@ protected function displayFirewallSummary(string $name, FirewallContext $context ); } - private function displaySwitchUser(FirewallContext $context, SymfonyStyle $io) + private function displaySwitchUser(FirewallContext $context, SymfonyStyle $io): void { if ((null === $config = $context->getConfig()) || (null === $switchUser = $config->getSwitchUser())) { return; @@ -216,11 +216,9 @@ private function displayAuthenticators(string $name, SymfonyStyle $io): void $io->table( ['Classname'], array_map( - static function ($authenticator) { - return [ - $authenticator::class, - ]; - }, + static fn ($authenticator) => [ + $authenticator::class, + ], $authenticators ) ); @@ -230,7 +228,7 @@ private function formatCallable(mixed $callable): string { if (\is_array($callable)) { if (\is_object($callable[0])) { - return sprintf('%s::%s()', \get_class($callable[0]), $callable[1]); + return sprintf('%s::%s()', $callable[0]::class, $callable[1]); } return sprintf('%s::%s()', $callable[0], $callable[1]); diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index 96730d041c5fc..14b95bff007b1 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -55,7 +55,7 @@ public function __construct(TokenStorageInterface $tokenStorage = null, RoleHier $this->hasVarDumper = class_exists(ClassStub::class); } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { if (null === $this->tokenStorage) { $this->data = [ @@ -145,7 +145,7 @@ public function collect(Request $request, Response $response, \Throwable $except foreach ($decisionLog as $key => $log) { $decisionLog[$key]['voter_details'] = []; foreach ($log['voterDetails'] as $voterDetail) { - $voterClass = \get_class($voterDetail['voter']); + $voterClass = $voterDetail['voter']::class; $classData = $this->hasVarDumper ? new ClassStub($voterClass) : $voterClass; $decisionLog[$key]['voter_details'][] = [ 'class' => $classData, @@ -202,12 +202,12 @@ public function collect(Request $request, Response $response, \Throwable $except $this->data['authenticators'] = $this->firewall ? $this->firewall->getAuthenticatorsInfo() : []; } - public function reset() + public function reset(): void { $this->data = []; } - public function lateCollect() + public function lateCollect(): void { $this->data = $this->cloneVar($this->data); } diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php index 45eae8f605202..bb5fe03096466 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php @@ -28,6 +28,9 @@ final class TraceableFirewallListener extends FirewallListener private array $wrappedListeners = []; private array $authenticatorsInfo = []; + /** + * @return array + */ public function getWrappedListeners() { return $this->wrappedListeners; @@ -38,7 +41,7 @@ public function getAuthenticatorsInfo(): array return $this->authenticatorsInfo; } - protected function callListeners(RequestEvent $event, iterable $listeners) + protected function callListeners(RequestEvent $event, iterable $listeners): void { $wrappedListeners = []; $wrappedLazyListeners = []; diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php index 357d2414d8bf2..0c4ff9e5cfb90 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php @@ -30,12 +30,12 @@ trait TraceableListenerTrait /** * Proxies all method calls to the original listener. */ - public function __call(string $method, array $arguments) + public function __call(string $method, array $arguments): mixed { return $this->listener->{$method}(...$arguments); } - public function getWrappedListener() + public function getWrappedListener(): mixed { return $this->listener; } diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php index a30900a5342e2..55c70ec5780d6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php @@ -38,12 +38,12 @@ public function supports(Request $request): ?bool return $this->listener->supports($request); } - public function authenticate(RequestEvent $event) + public function authenticate(RequestEvent $event): void { $startTime = microtime(true); try { - $ret = $this->listener->authenticate($event); + $ 10000 this->listener->authenticate($event); } catch (LazyResponseException $e) { $this->response = $e->getResponse(); @@ -53,7 +53,5 @@ public function authenticate(RequestEvent $event) } $this->response = $event->getResponse(); - - return $ret; } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php index 607d1f06c927d..08d7fd9213df8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php @@ -22,6 +22,9 @@ */ class AddExpressionLanguageProvidersPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if ($container->has('security.expression_language')) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php index 273a0db67fddf..ccf474087af5d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php @@ -29,6 +29,9 @@ class AddSecurityVotersPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('security.access.decision_manager')) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php index 5b107b5ef0dfc..9a7a94ca08786 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php @@ -21,6 +21,9 @@ */ class AddSessionDomainConstraintPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasParameter('session.storage.options') || !$container->has('security.http_utils')) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php index fb4b6f83dc3b8..2041a36b3806d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php @@ -21,6 +21,9 @@ */ class CleanRememberMeVerifierPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('cache.system')) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php new file mode 100644 index 0000000000000..e7c77d1ec31d8 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePass.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; + +/** + * @author Mathieu Lechat + */ +class MakeFirewallsEventDispatcherTraceablePass implements CompilerPassInterface +{ + /** + * @return void + */ + public function process(ContainerBuilder $container) + { + if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) { + return; + } + + if (!$container->getParameter('kernel.debug') || !$container->has('debug.stopwatch')) { + return; + } + + $dispatchersId = []; + + foreach ($container->getParameter('security.firewalls') as $firewallName) { + $dispatcherId = 'security.event_dispatcher.'.$firewallName; + + if (!$container->has($dispatcherId)) { + continue; + } + + $dispatchersId[$dispatcherId] = 'debug.'.$dispatcherId; + + $container->register($dispatchersId[$dispatcherId], TraceableEventDispatcher::class) + ->setDecoratedService($dispatcherId) + ->setArguments([ + new Reference($dispatchersId[$dispatcherId].'.inner'), + new Reference('debug.stopwatch'), + new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE), + new Reference('request_stack', ContainerInterface::NULL_ON_INVALID_REFERENCE), + ]); + } + + foreach (['kernel.event_subscriber', 'kernel.event_listener'] as $tagName) { + foreach ($container->findTaggedServiceIds($tagName) as $taggedServiceId => $tags) { + $taggedServiceDefinition = $container->findDefinition($taggedServiceId); + $taggedServiceDefinition->clearTag($tagName); + + foreach ($tags as $tag) { + if ($dispatcherId = $tag['dispatcher'] ?? null) { + $tag['dispatcher'] = $dispatchersId[$dispatcherId] ?? $dispatcherId; + } + $taggedServiceDefinition->addTag($tagName, $tag); + } + } + } + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php index a0d6206134031..3564f7c1fd38a 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php @@ -26,13 +26,13 @@ */ class RegisterCsrfFeaturesPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { $this->registerCsrfProtectionListener($container); $this->registerLogoutHandler($container); } - private function registerCsrfProtectionListener(ContainerBuilder $container) + private function registerCsrfProtectionListener(ContainerBuilder $container): void { if (!$container->has('security.authenticator.manager') || !$container->has('security.csrf.token_manager')) { return; @@ -44,7 +44,7 @@ private function registerCsrfProtectionListener(ContainerBuilder $container) ->setPublic(false); } - protected function registerLogoutHandler(ContainerBuilder $container) + protected function registerLogoutHandler(ContainerBuilder $container): void { if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) { return; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php index 6de49517f3405..3ca2a70acb934 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php @@ -23,6 +23,9 @@ */ class RegisterEntryPointPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasParameter('security.firewalls')) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php index 8eac2cf83a8c0..5d581aac7ccd8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php @@ -52,7 +52,7 @@ class RegisterGlobalSecurityEventListenersPass implements CompilerPassInterface SecurityEvents::INTERACTIVE_LOGIN, ]; - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) { return; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterLdapLocatorPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterLdapLocatorPass.php index 295f363292245..8221aa53aa783 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterLdapLocatorPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterLdapLocatorPass.php @@ -25,7 +25,7 @@ */ class RegisterLdapLocatorPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { $definition = $container->setDefinition('security.ldap_locator', new Definition(ServiceLocator::class)); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php index fdffab1bbf395..1d1e47cfe15ec 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php @@ -27,7 +27,7 @@ */ class RegisterTokenUsageTrackingPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->has('security.untracked_token_storage')) { return; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/SortFirewallListenersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/SortFirewallListenersPass.php index 6d49320445c10..7f0301a3edab7 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/SortFirewallListenersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/SortFirewallListenersPass.php @@ -45,9 +45,7 @@ private function sortFirewallContextListeners(Definition $definition, ContainerB $prioritiesByServiceId = $this->getListenerPriorities($listenerIteratorArgument, $container); $listeners = $listenerIteratorArgument->getValues(); - usort($listeners, function (Reference $a, Reference $b) use ($prioritiesByServiceId) { - return $prioritiesByServiceId[(string) $b] <=> $prioritiesByServiceId[(string) $a]; - }); + usort($listeners, fn (Reference $a, Reference $b) => $prioritiesByServiceId[(string) $b] <=> $prioritiesByServiceId[(string) $a]); $listenerIteratorArgument->setValues(array_values($listeners)); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index 25778ea851dd7..e982fc1871940 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -40,7 +40,7 @@ class MainConfiguration implements ConfigurationInterface private array $userProviderFactories; /** - * @param array $factories + * @param array $factories */ public function __construct(array $factories, array $userProviderFactories) { @@ -78,15 +78,15 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end() ->end() ->validate() - ->ifTrue(function ($v) { return isset($v['strategy'], $v['service']); }) + ->ifTrue(fn ($v) => isset($v['strategy'], $v['service'])) ->thenInvalid('"strategy" and "service" cannot be used together.') ->end() ->validate() - ->ifTrue(function ($v) { return isset($v['strategy'], $v['strategy_service']); }) + ->ifTrue(fn ($v) => isset($v['strategy'], $v['strategy_service'])) ->thenInvalid('"strategy" and "strategy_service" cannot be used together.') ->end() ->validate() - ->ifTrue(function ($v) { return isset($v['service'], $v['strategy_service']); }) + ->ifTrue(fn ($v) => isset($v['service'], $v['strategy_service'])) ->thenInvalid('"service" and "strategy_service" cannot be used together.') ->end() ->end() @@ -102,7 +102,7 @@ public function getConfigTreeBuilder(): TreeBuilder return $tb; } - private function addRoleHierarchySection(ArrayNodeDefinition $rootNode) + private function addRoleHierarchySection(ArrayNodeDefinition $rootNode): void { $rootNode ->fixXmlConfig('role', 'role_hierarchy') @@ -111,10 +111,10 @@ private function addRoleHierarchySection(ArrayNodeDefinition $rootNode) ->useAttributeAsKey('id') ->prototype('array') ->performNoDeepMerging() - ->beforeNormalization()->ifString()->then(function ($v) { return ['value' => $v]; })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => ['value' => $v])->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && isset($v['value']); }) - ->then(function ($v) { return preg_split('/\s*,\s*/', $v['value']); }) + ->ifTrue(fn ($v) => \is_array($v) && isset($v['value'])) + ->then(fn ($v) => preg_split('/\s*,\s*/', $v['value'])) ->end() ->prototype('scalar')->end() ->end() @@ -123,7 +123,7 @@ private function addRoleHierarchySection(ArrayNodeDefinition $rootNode) ; } - private function addAccessControlSection(ArrayNodeDefinition $rootNode) + private function addAccessControlSection(ArrayNodeDefinition $rootNode): void { $rootNode ->fixXmlConfig('rule', 'access_control') @@ -173,9 +173,9 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode) } /** - * @param array $factories + * @param array $factories */ - private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories) + private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $factories): void { $firewallNodeBuilder = $rootNode ->fixXmlConfig('firewall') @@ -217,12 +217,20 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->treatTrueLike([]) ->canBeUnset() ->beforeNormalization() - ->ifTrue(fn ($v): bool => \is_array($v) && (isset($v['csrf_token_generator']) xor isset($v['enable_csrf']))) + ->ifTrue(fn ($v): bool => isset($v['csrf_token_generator']) && !isset($v['csrf_token_manager'])) ->then(function (array $v): array { - if (isset($v['csrf_token_generator'])) { + $v['csrf_token_manager'] = $v['csrf_token_generator']; + + return $v; + }) + ->end() + ->beforeNormalization() + ->ifTrue(fn ($v): bool => \is_array($v) && (isset($v['csrf_token_manager']) xor isset($v['enable_csrf']))) + ->then(function (array $v): array { + if (isset($v['csrf_token_manager'])) { $v['enable_csrf'] = true; } elseif ($v['enable_csrf']) { - $v['csrf_token_generator'] = 'security.csrf.token_manager'; + $v['csrf_token_manager'] = 'security.csrf.token_manager'; } return $v; @@ -232,10 +240,26 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->booleanNode('enable_csrf')->defaultNull()->end() ->scalarNode('csrf_token_id')->defaultValue('logout')->end() ->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end() - ->scalarNode('csrf_token_generator')->end() + ->scalarNode('csrf_token_generator') + ->setDeprecated( + 'symfony/security-bundle', + '6.3', + 'The "%node%" option is deprecated. Use "csrf_token_manager" instead.' + ) + ->end() + ->scalarNode('csrf_token_manager')->end() ->scalarNode('path')->defaultValue('/logout')->end() ->scalarNode('target')->defaultValue('/')->end() ->booleanNode('invalidate_session')->defaultTrue()->end() + ->arrayNode('clear_site_data') + ->performNoDeepMerging() + ->beforeNormalization()->ifString()->then(fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end() + ->enumPrototype() + ->values([ + '*', 'cache', 'cookies', 'storage', 'executionContexts', + ]) + ->end() + ->end() ->end() ->fixXmlConfig('delete_cookie') ->children() @@ -309,9 +333,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto $firewallNodeBuilder ->end() ->validate() - ->ifTrue(function ($v) { - return true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher']); - }) + ->ifTrue(fn ($v) => true === $v['security'] && isset($v['pattern']) && !isset($v['request_matcher'])) ->then(function ($firewall) use ($abstractFactoryKeys) { foreach ($abstractFactoryKeys as $k) { if (!isset($firewall[$k]['check_path'])) { @@ -329,7 +351,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ; } - private function addProvidersSection(ArrayNodeDefinition $rootNode) + private function addProvidersSection(ArrayNodeDefinition $rootNode): void { $providerNodeBuilder = $rootNode ->fixXmlConfig('provider') @@ -360,7 +382,7 @@ private function addProvidersSection(ArrayNodeDefinition $rootNode) ->arrayNode('providers') ->beforeNormalization() ->ifString() - ->then(function ($v) { return preg_split('/\s*,\s*/', $v); }) + ->then(fn ($v) => preg_split('/\s*,\s*/', $v)) ->end() ->prototype('scalar')->end() ->end() @@ -378,17 +400,17 @@ private function addProvidersSection(ArrayNodeDefinition $rootNode) $providerNodeBuilder ->validate() - ->ifTrue(function ($v) { return \count($v) > 1; }) + ->ifTrue(fn ($v) => \count($v) > 1) ->thenInvalid('You cannot set multiple provider types for the same provider') ->end() ->validate() - ->ifTrue(function ($v) { return 0 === \count($v); }) + ->ifTrue(fn ($v) => 0 === \count($v)) ->thenInvalid('You must set a provider definition for the provider.') ->end() ; } - private function addPasswordHashersSection(ArrayNodeDefinition $rootNode) + private function addPasswordHashersSection(ArrayNodeDefinition $rootNode): void { $rootNode ->fixXmlConfig('password_hasher') diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php new file mode 100644 index 0000000000000..6f19f3845cb15 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken; + +use Jose\Component\Core\Algorithm; +use Jose\Component\Core\JWK; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SignatureAlgorithmFactory; +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Configures a token handler for decoding and validating an OIDC token. + * + * @experimental + */ +class OidcTokenHandlerFactory implements TokenHandlerFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array|string $config): void + { + $tokenHandlerDefinition = $container->setDefinition($id, new ChildDefinition('security.access_token_handler.oidc')); + $tokenHandlerDefinition->replaceArgument(3, $config['claim']); + $tokenHandlerDefinition->replaceArgument(4, $config['audience']); + + // Create the signature algorithm and the JWK + if (!ContainerBuilder::willBeAvailable('web-token/jwt-core', Algorithm::class, ['symfony/security-bundle'])) { + $container->register('security.access_token_handler.oidc.signature', 'stdClass') + ->addError('You cannot use the "oidc" token handler since "web-token/jwt-core" is not installed. Try running "web-token/jwt-core".'); + $container->register('security.access_token_handler.oidc.jwk', 'stdClass') + ->addError('You cannot use the "oidc" token handler since "web-token/jwt-core" is not installed. Try running "web-token/jwt-core".'); + } else { + $container->register('security.access_token_handler.oidc.signature', Algorithm::class) + ->setFactory([SignatureAlgorithmFactory::class, 'create']) + ->setArguments([$config['signature']['algorithm']]); + $container->register('security.access_token_handler.oidc.jwk', JWK::class) + ->setFactory([JWK::class, 'createFromJson']) + ->setArguments([$config['signature']['key']]); + } + $tokenHandlerDefinition->replaceArgument(0, new Reference('security.access_token_handler.oidc.signature')); + $tokenHandlerDefinition->replaceArgument(1, new Reference('security.access_token_handler.oidc.jwk')); + } + + public function getKey(): string + { + return 'oidc'; + } + + public function addConfiguration(NodeBuilder $node): void + { + $node + ->arrayNode($this->getKey()) + ->fixXmlConfig($this->getKey()) + ->children() + ->scalarNode('claim') + ->info('Claim which contains the user identifier (e.g.: sub, email..).') + ->defaultValue('sub') + ->end() + ->scalarNode('audience') + ->info('Audience set in the token, for validation purpose.') + ->defaultNull() + ->end() + ->arrayNode('signature') + ->isRequired() + ->children() + ->scalarNode('algorithm') + ->info('Algorithm used to sign the token.') + ->isRequired() + ->end() + ->scalarNode('key') + ->info('JSON-encoded JWK used to sign the token (must contain a "kty" key).') + ->isRequired() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php new file mode 100644 index 0000000000000..08b1019f2c210 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpClient\HttpClient; + +/** + * Configures a token handler for an OIDC server. + * + * @experimental + */ +class OidcUserInfoTokenHandlerFactory implements TokenHandlerFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array|string $config): void + { + $tokenHandlerDefinition = $container->setDefinition($id, new ChildDefinition('security.access_token_handler.oidc_user_info')); + $tokenHandlerDefinition->replaceArgument(2, $config['claim']); + + // Create the client service + if (!isset($config['client']['id'])) { + $clientDefinitionId = 'http_client.security.access_token_handler.oidc_user_info'; + if (!ContainerBuilder::willBeAvailable('symfony/http-client', HttpClient::class, ['symfony/security-bundle'])) { + $container->register($clientDefinitionId, 'stdClass') + ->addError('You cannot use the "oidc_user_info" token handler since the HttpClient component is not installed. Try running "composer require symfony/http-client".'); + } else { + $container->register($clientDefinitionId, HttpClient::class) + ->setFactory([HttpClient::class, 'create']) + ->setArguments([$config['client']]) + ->addTag('http_client.client'); + } + } + + $tokenHandlerDefinition->replaceArgument(0, new Reference($config['client']['id'] ?? $clientDefinitionId)); + } + + public function getKey(): string + { + return 'oidc_user_info'; + } + + public function addConfiguration(NodeBuilder $node): void + { + $node + ->arrayNode($this->getKey()) + ->fixXmlConfig($this->getKey()) + ->children() + ->scalarNode('claim') + ->info('Claim which contains the user identifier (e.g.: sub, email..).') + ->defaultValue('sub') + ->end() + ->arrayNode('client') + ->info('HttpClient to call the OIDC server.') + ->isRequired() + ->beforeNormalization() + ->ifString() + ->then(static function ($v): array { return ['id' => $v]; }) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/ServiceTokenHandlerFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/ServiceTokenHandlerFactory.php new file mode 100644 index 0000000000000..f38a70db99417 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/ServiceTokenHandlerFactory.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Configures a token handler from a service id. + * + * @see \Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory\AccessTokenFactoryTest + * + * @experimental + */ +class ServiceTokenHandlerFactory implements TokenHandlerFactoryInterface +{ + public function create(ContainerBuilder $container, string $id, array|string $config): void + { + $container->setDefinition($id, new ChildDefinition($config)); + } + + public function getKey(): string + { + return 'id'; + } + + public function addConfiguration(NodeBuilder $node): void + { + $node->scalarNode($this->getKey())->end(); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/TokenHandlerFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/TokenHandlerFactoryInterface.php new file mode 100644 index 0000000000000..bfa9535e7544e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/TokenHandlerFactoryInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken; + +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Allows creating configurable token handlers. + * + * @experimental + */ +interface TokenHandlerFactoryInterface +{ + /** + * Creates a generic token handler service. + */ + public function create(ContainerBuilder $container, string $id, array|string $config): void; + + /** + * Gets a generic token handler configuration key. + */ + public function getKey(): string; + + /** + * Adds a generic token handler configuration. + */ + public function addConfiguration(NodeBuilder $node): void; +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php index 1d2121ff1616a..24eb1377c51c2 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php @@ -52,6 +52,9 @@ final public function addOption(string $name, mixed $default = null): void $this->options[$name] = $default; } + /** + * @return void + */ public function addConfiguration(NodeDefinition $node) { $builder = $node->children(); @@ -72,6 +75,9 @@ public function addConfiguration(NodeDefinition $node) } } + /** + * @return string + */ protected function createAuthenticationSuccessHandler(ContainerBuilder $container, string $id, array $config) { $successHandlerId = $this->getSuccessHandlerId($id); @@ -91,6 +97,9 @@ protected function createAuthenticationSuccessHandler(ContainerBuilder $containe return $successHandlerId; } + /** + * @return string + */ protected function createAuthenticationFailureHandler(ContainerBuilder $container, string $id, array $config) { $id = $this->getFailureHandlerId($id); @@ -108,11 +117,17 @@ protected function createAuthenticationFailureHandler(ContainerBuilder $containe return $id; } + /** + * @return string + */ protected function getSuccessHandlerId(string $id) { return 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey()); } + /** + * @return string + */ protected function getFailureHandlerId(string $id) { return 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey()); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php index 2fbf3b2f8b567..04e419cd82d6b 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php @@ -11,7 +11,9 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\TokenHandlerFactoryInterface; use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -23,11 +25,14 @@ * * @internal */ -final class AccessTokenFactory extends AbstractFactory +final class AccessTokenFactory extends AbstractFactory implements StatelessAuthenticatorFactoryInterface { private const PRIORITY = -40; - public function __construct() + /** + * @param array $tokenHandlerFactories + */ + public function __construct(private readonly array $tokenHandlerFactories) { $this->options = []; $this->defaultFailureHandlerOptions = []; @@ -40,13 +45,12 @@ public function addConfiguration(NodeDefinition $node): void $builder = $node->children(); $builder - ->scalarNode('token_handler')->isRequired()->end() ->scalarNode('realm')->defaultNull()->end() ->arrayNode('token_extractors') ->fixXmlConfig('token_extractors') ->beforeNormalization() ->ifString() - ->then(static function (string $v): array { return [$v]; }) + ->then(static fn (string $v): array => [$v]) ->end() ->cannotBeEmpty() ->defaultValue([ @@ -55,6 +59,38 @@ public function addConfiguration(NodeDefinition $node): void ->scalarPrototype()->end() ->end() ; + + $tokenHandlerNodeBuilder = $builder + ->arrayNode('token_handler') + ->example([ + 'id' => 'App\Security\CustomTokenHandler', + ]) + + ->beforeNormalization() + ->ifString() + ->then(static function (string $v): array { return ['id' => $v]; }) + ->end() + + ->beforeNormalization() + ->ifTrue(static function ($v) { return \is_array($v) && 1 < \count($v); }) + ->then(static function () { throw new InvalidConfigurationException('You cannot configure multiple token handlers.'); }) + ->end() + + // "isRequired" must be set otherwise the following custom validation is not called + ->isRequired() + ->beforeNormalization() + ->ifTrue(static function ($v) { return \is_array($v) && !$v; }) + ->then(static function () { throw new InvalidConfigurationException('You must set a token handler.'); }) + ->end() + + ->children() + ; + + foreach ($this->tokenHandlerFactories as $factory) { + $factory->addConfiguration($tokenHandlerNodeBuilder); + } + + $tokenHandlerNodeBuilder->end(); } public function getPriority(): int @@ -67,18 +103,19 @@ public function getKey(): string return 'access_token'; } - public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string { $successHandler = isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null; $failureHandler = isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null; $authenticatorId = sprintf('security.authenticator.access_token.%s', $firewallName); $extractorId = $this->createExtractor($container, $firewallName, $config['token_extractors']); + $tokenHandlerId = $this->createTokenHandler($container, $firewallName, $config['token_handler'], $userProviderId); $container ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.access_token')) - ->replaceArgument(0, new Reference($config['token_handler'])) + ->replaceArgument(0, new Reference($tokenHandlerId)) ->replaceArgument(1, new Reference($extractorId)) - ->replaceArgument(2, new Reference($userProviderId)) + ->replaceArgument(2, $userProviderId ? new Reference($userProviderId) : null) ->replaceArgument(3, $successHandler) ->replaceArgument(4, $failureHandler) ->replaceArgument(5, $config['realm']) @@ -97,9 +134,7 @@ private function createExtractor(ContainerBuilder $container, string $firewallNa 'request_body' => 'security.access_token_extractor.request_body', 'header' => 'security.access_token_extractor.header', ]; - $extractors = array_map(static function (string $extractor) use ($aliases): string { - return $aliases[$extractor] ?? $extractor; - }, $extractors); + $extractors = array_map(static fn (string $extractor): string => $aliases[$extractor] ?? $extractor, $extractors); if (1 === \count($extractors)) { return current($extractors); @@ -107,9 +142,25 @@ private function createExtractor(ContainerBuilder $container, string $firewallNa $extractorId = sprintf('security.authenticator.access_token.chain_extractor.%s', $firewallName); $container ->setDefinition($extractorId, new ChildDefinition('security.authenticator.access_token.chain_extractor')) - ->replaceArgument(0, array_map(function (string $extractorId): Reference {return new Reference($extractorId); }, $extractors)) + ->replaceArgument(0, array_map(fn (string $extractorId): Reference => new Reference($extractorId), $extractors)) ; return $extractorId; } + + private function createTokenHandler(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string + { + $key = array_keys($config)[0]; + $id = sprintf('security.access_token_handler.%s', $firewallName); + + foreach ($this->tokenHandlerFactories as $factory) { + if ($key !== $factory->getKey()) { + continue; + } + + $factory->create($container, $id, $config[$key], $userProviderId); + } + + return $id; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php index 1ef3f74f79aa5..8082b6f3524b5 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php @@ -30,11 +30,16 @@ public function getPriority(): int; */ public function getKey(): string; + /** + * @return void + */ public function addConfiguration(NodeDefinition $builder); /** * Creates the authenticator service(s) for the provided configuration. * + * @param array $config + * * @return string|string[] The authenticator service ID(s) to be used by the firewall */ public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string|array; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php index 269b6e85a925d..e443122e6cf10 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php @@ -35,7 +35,7 @@ public function getKey(): string /** * @param ArrayNodeDefinition $builder */ - public function addConfiguration(NodeDefinition $builder) + public function addConfiguration(NodeDefinition $builder): void { $builder ->info('An array of service ids for all of your "authenticators"') @@ -47,7 +47,7 @@ public function addConfiguration(NodeDefinition $builder) $factoryRootNode ->fixXmlConfig('custom_authenticator') ->validate() - ->ifTrue(function ($v) { return isset($v['custom_authenticators']) && empty($v['custom_authenticators']); }) + ->ifTrue(fn ($v) => isset($v['custom_authenticators']) && empty($v['custom_authenticators'])) ->then(function ($v) { unset($v['custom_authenticators']); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.php index c4842010a779f..443ced6c4c936 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FirewallListenerFactoryInterface.php @@ -23,6 +23,8 @@ interface FirewallListenerFactoryInterface /** * Creates the firewall listener services for the provided configuration. * + * @param array $config + * * @return string[] The listener service IDs to be used by the firewall */ public function createListeners(ContainerBuilder $container, string $firewallName, array $config): array; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php index bc5ce52376c0f..177fda4feb5a4 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php @@ -50,7 +50,7 @@ public function getKey(): string return 'form-login'; } - public function addConfiguration(NodeDefinition $node) + public function addConfiguration(NodeDefinition $node): void { parent::addConfiguration($node); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php index a439ca0adf316..705d079c5d73e 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php @@ -25,7 +25,7 @@ class FormLoginLdapFactory extends FormLoginFactory { use LdapFactoryTrait; - public function addConfiguration(NodeDefinition $node) + public function addConfiguration(NodeDefinition $node): void { parent::addConfiguration($node); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php index 02f2009ac1eca..45d78508de6bc 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php @@ -48,7 +48,7 @@ public function getKey(): string return 'http-basic'; } - public function addConfiguration(NodeDefinition $node) + public function addConfiguration(NodeDefinition $node): void { $node ->children() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php index 0c63b21c63aaa..3d7946115c433 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php @@ -70,7 +70,7 @@ public function create(ContainerBuilder $container, string $id, array $config, s return [$provider, $listenerId, $entryPointId]; } - public function addConfiguration(NodeDefinition $node) + public function addConfiguration(NodeDefinition $node): void { parent::addConfiguration($node); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php index 3b4ff7a048df2..61266854c8f5e 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php @@ -22,7 +22,7 @@ class JsonLoginLdapFactory extends JsonLoginFactory { use LdapFactoryTrait; - public function addConfiguration(NodeDefinition $node) + public function addConfiguration(NodeDefinition $node): void { parent::addConfiguration($node); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginLinkFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginLinkFactory.php index 29b588631bc0a..9a03a0f066744 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginLinkFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginLinkFactory.php @@ -28,7 +28,7 @@ class LoginLinkFactory extends AbstractFactory { public const PRIORITY = -20; - public function addConfiguration(NodeDefinition $node) + public function addConfiguration(NodeDefinition $node): void { /** @var NodeBuilder $builder */ $builder = $node->fixXmlConfig('signature_property', 'signature_properties')->children(); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php index 6356702cc5d0b..4092f3e837f4c 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php @@ -44,7 +44,7 @@ public function getKey(): string /** * @param ArrayNodeDefinition $builder */ - public function addConfiguration(NodeDefinition $builder) + public function addConfiguration(NodeDefinition $builder): void { $builder ->children() @@ -87,7 +87,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal return []; } - private function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig) + private function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig): void { // default configuration (when used by other DI extensions) $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index d3ec4633cf5ce..ca9f0ae1787bd 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -70,10 +70,9 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal $tokenVerifier = $this->createTokenVerifier($container, $firewallName, $config['token_verifier'] ?? null); $container->setDefinition($rememberMeHandlerId, new ChildDefinition('security.authenticator.persistent_remember_me_handler')) ->replaceArgument(0, new Reference($tokenProviderId)) - ->replaceArgument(1, $config['secret']) - ->replaceArgument(2, new Reference($userProviderId)) - ->replaceArgument(4, $config) - ->replaceArgument(6, $tokenVerifier) + ->replaceArgument(1, new Reference($userProviderId)) + ->replaceArgument(3, $config) + ->replaceArgument(5, $tokenVerifier) ->addTag('security.remember_me_handler', ['firewall' => $firewallName]); } else { $signatureHasherId = 'security.authenticator.remember_me_signature_hasher.'.$firewallName; @@ -133,7 +132,7 @@ public function getKey(): string return 'remember-me'; } - public function addConfiguration(NodeDefinition $node) + public function addConfiguration(NodeDefinition $node): void { $builder = $node ->fixXmlConfig('user_provider') @@ -148,7 +147,7 @@ public function addConfiguration(NodeDefinition $node) ->scalarNode('service')->end() ->arrayNode('user_providers') ->beforeNormalization() - ->ifString()->then(function ($v) { return [$v]; }) + ->ifString()->then(fn ($v) => [$v]) ->end() ->prototype('scalar')->end() ->end() @@ -162,7 +161,7 @@ public function addConfiguration(NodeDefinition $node) ->end() ->arrayNode('token_provider') ->beforeNormalization() - ->ifString()->then(function ($v) { return ['service' => $v]; }) + ->ifString()->then(fn ($v) => ['service' => $v]) ->end() ->children() ->scalarNode('service')->info('The service ID of a custom rememberme token provider.')->end() @@ -235,7 +234,7 @@ private function createTokenVerifier(ContainerBuilder $container, string $firewa return new Reference($tokenVerifierId, ContainerInterface::NULL_ON_INVALID_REFERENCE); } - public function prepend(ContainerBuilder $container) + public function prepend(ContainerBuilder $container): void { $rememberMeSecureDefault = false; $rememberMeSameSiteDefault = null; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php index de79af1494f42..97d50008b5baf 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php @@ -51,7 +51,7 @@ public function getKey(): string return 'remote-user'; } - public function addConfiguration(NodeDefinition $node) + public function addConfiguration(NodeDefinition $node): void { $node ->children() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SignatureAlgorithmFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SignatureAlgorithmFactory.php new file mode 100644 index 0000000000000..f9f876deff2bf --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SignatureAlgorithmFactory.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Jose\Component\Core\Algorithm as SignatureAlgorithm; +use Jose\Component\Signature\Algorithm; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; +use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler; + +/** + * Creates a signature algorithm for {@see OidcTokenHandler}. + * + * @experimental + */ +final class SignatureAlgorithmFactory +{ + public static function create(string $algorithm): SignatureAlgorithm + { + switch ($algorithm) { + case 'ES256': + if (!class_exists(Algorithm\ES256::class)) { + throw new \LogicException('You cannot use the "ES256" signature algorithm since "web-token/jwt-signature-algorithm-ecdsa" is not installed. Try running "composer require web-token/jwt-signature-algorithm-ecdsa".'); + } + + return new Algorithm\ES256(); + case 'ES384': + if (!class_exists(Algorithm\ES384::class)) { + throw new \LogicException('You cannot use the "ES384" signature algorithm since "web-token/jwt-signature-algorithm-ecdsa" is not installed. Try running "composer require web-token/jwt-signature-algorithm-ecdsa".'); + } + + return new Algorithm\ES384(); + case 'ES512': + if (!class_exists(Algorithm\ES512::class)) { + throw new \LogicException('You cannot use the "ES512" signature algorithm since "web-token/jwt-signature-algorithm-ecdsa" is not installed. Try running "composer require web-token/jwt-signature-algorithm-ecdsa".'); + } + + return new Algorithm\ES512(); + default: + throw new InvalidArgumentException(sprintf('Unsupported signature algorithm "%s". Only ES* algorithms are supported. If you want to use another algorithm, create your TokenHandler as a service.', $algorithm)); + } + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/StatelessAuthenticatorFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/StatelessAuthenticatorFactoryInterface.php new file mode 100644 index 0000000000000..4d536019b36e7 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/StatelessAuthenticatorFactoryInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Stateless authenticators are authenticators that can work without a user provider. + * + * This situation can only occur in stateless firewalls, as statefull firewalls + * need the user provider to refresh the user in each subsequent request. A + * stateless authenticator can be used on both stateless and statefull authenticators. + * + * @author Wouter de Jong + */ +interface StatelessAuthenticatorFactoryInterface extends AuthenticatorFactoryInterface +{ + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, ?string $userProviderId): string|array; +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php index f59783defd11c..2d28e2b684b74 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php @@ -36,6 +36,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal ->replaceArgument(2, $firewallName) ->replaceArgument(3, $config['user']) ->replaceArgument(4, $config['credentials']) + ->replaceArgument(6, $config['user_identifier']) ; return $authenticatorId; @@ -51,13 +52,14 @@ public function getKey(): string return 'x509'; } - public function addConfiguration(NodeDefinition $node) + public function addConfiguration(NodeDefinition $node): void { $node ->children() ->scalarNode('provider')->end() ->scalarNode('user')->defaultValue('SSL_CLIENT_S_DN_Email')->end() ->scalarNode('credentials')->defaultValue('SSL_CLIENT_S_DN')->end() + ->scalarNode('user_identifier')->defaultValue('emailAddress')->end() ->end() ; } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php index 0abb1ce247f5e..936f58a084222 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php @@ -24,6 +24,9 @@ */ class InMemoryFactory implements UserProviderFactoryInterface { + /** + * @return void + */ public function create(ContainerBuilder $container, string $id, array $config) { $definition = $container->setDefinition($id, new ChildDefinition('security.user.provider.in_memory')); @@ -37,11 +40,17 @@ public function create(ContainerBuilder $container, string $id, array $config) $definition->addArgument($users); } + /** + * @return string + */ public function getKey() { return 'memory'; } + /** + * @return void + */ public function addConfiguration(NodeDefinition $node) { $node @@ -54,7 +63,7 @@ public function addConfiguration(NodeDefinition $node) ->children() ->scalarNode('password')->defaultNull()->end() ->arrayNode('roles') - ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end() ->prototype('scalar')->end() ->end() ->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php index c2a334369ca0c..2f4dca01d1598 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php @@ -24,6 +24,9 @@ */ class LdapFactory implements UserProviderFactoryInterface { + /** + * @return void + */ public function create(ContainerBuilder $container, string $id, array $config) { $container @@ -40,11 +43,17 @@ public function create(ContainerBuilder $container, string $id, array $config) ; } + /** + * @return string + */ public function getKey() { return 'ldap'; } + /** + * @return void + */ public function addConfiguration(NodeDefinition $node) { $node @@ -59,7 +68,7 @@ public function addConfiguration(NodeDefinition $node) ->prototype('scalar')->end() ->end() ->arrayNode('default_roles') - ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() + ->beforeNormalization()->ifString()->then(fn ($v) => preg_split('/\s*,\s*/', $v))->end() ->requiresAtLeastOneElement() ->prototype('scalar')->end() ->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php index 6d9481c59cb4a..a2c5815e4bfac 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/UserProviderFactoryInterface.php @@ -22,9 +22,18 @@ */ interface UserProviderFactoryInterface { + /** + * @return void + */ public function create(ContainerBuilder $container, string $id, array $config); + /** + * @return string + */ public function getKey(); + /** + * @return void + */ public function addConfiguration(NodeDefinition $builder); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index cd514bb44367d..37978b285f3d7 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -14,7 +14,9 @@ use Symfony\Bridge\Twig\Extension\LogoutUrlExt 10000 ension; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\StatelessAuthenticatorFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; +use Symfony\Bundle\SecurityBundle\SecurityBundle; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\FileLocator; @@ -76,6 +78,9 @@ class SecurityExtension extends Extension implements PrependExtensionInterface private array $sortedFactories = []; private array $userProviderFactories = []; + /** + * @return void + */ public function prepend(ContainerBuilder $container) { foreach ($this->getSortedFactories() as $factory) { @@ -85,9 +90,15 @@ public function prepend(ContainerBuilder $container) } } + /** + * @return void + */ public function load(array $configs, ContainerBuilder $container) { if (!array_filter($configs)) { + trigger_deprecation('symfony/security-bundle', '6.3', 'Enabling bundle "%s" and not configuring it is deprecated.', SecurityBundle::class); + // uncomment the following line in 7.0 + // throw new InvalidArgumentException(sprintf('Enabling bundle "%s" and not configuring it is not allowed.', SecurityBundle::class)); return; } @@ -195,7 +206,7 @@ private function createStrategyDefinition(string $strategy, bool $allowIfAllAbst }; } - private function createRoleHierarchy(array $config, ContainerBuilder $container) + private function createRoleHierarchy(array $config, ContainerBuilder $container): void { if (!isset($config['role_hierarchy']) || 0 === \count($config['role_hierarchy'])) { $container->removeDefinition('security.access.role_hierarchy_voter'); @@ -207,7 +218,7 @@ private function createRoleHierarchy(array $config, ContainerBuilder $container) $container->removeDefinition('security.access.simple_role_voter'); } - private function createAuthorization(array $config, ContainerBuilder $container) + private function createAuthorization(array $config, ContainerBuilder $container): void { foreach ($config['access_control'] as $access) { if (isset($access['request_matcher'])) { @@ -260,7 +271,7 @@ private function createAuthorization(array $config, ContainerBuilder $container) } } - private function createFirewalls(array $config, ContainerBuilder $container) + private function createFirewalls(array $config, ContainerBuilder $container): void { if (!isset($config['firewalls'])) { return; @@ -348,7 +359,7 @@ private function createFirewalls(array $config, ContainerBuilder $container) } } - private function createFirewall(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, array $providerIds, string $configId) + private function createFirewall(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, array $providerIds, string $configId): array { $config = $container->setDefinition($configId, new ChildDefinition('security.firewall.config')); $config->replaceArgument(0, $id); @@ -446,14 +457,13 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ 'logout_path' => $firewall['logout']['path'], ]); - $logoutSuccessListenerId = 'security.logout.listener.default.'.$id; - $container->setDefinition($logoutSuccessListenerId, new ChildDefinition('security.logout.listener.default')) + $container->setDefinition('security.logout.listener.default.'.$id, new ChildDefinition('security.logout.listener.default')) ->replaceArgument(1, $firewall['logout']['target']) ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); // add CSRF provider if ($firewall['logout']['enable_csrf']) { - $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_generator'])); + $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_manager'])); } // add session logout listener @@ -469,6 +479,13 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); } + // add clear site data listener + if ($firewall['logout']['clear_site_data'] ?? false) { + $container->setDefinition('security.logout.listener.clear_site_data.'.$id, new ChildDefinition('security.logout.listener.clear_site_data')) + ->addArgument($firewall['logout']['clear_site_data']) + ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); + } + // register with LogoutUrlGenerator $container ->getDefinition('security.logout_url_generator') @@ -477,7 +494,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ $firewall['logout']['path'], $firewall['logout']['csrf_token_id'], $firewall['logout']['csrf_parameter'], - isset($firewall['logout']['csrf_token_generator']) ? new Reference($firewall['logout']['csrf_token_generator']) : null, + isset($firewall['logout']['csrf_token_manager']) ? new Reference($firewall['logout']['csrf_token_manager']) : null, false === $firewall['stateless'] && isset($firewall['context']) ? $firewall['context'] : null, ]) ; @@ -496,9 +513,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ $configuredEntryPoint = $defaultEntryPoint; // authenticator manager - $authenticators = array_map(function ($id) { - return new Reference($id); - }, $firewallAuthenticationProviders); + $authenticators = array_map(fn ($id) => new Reference($id), $firewallAuthenticationProviders); $container ->setDefinition($managerId = 'security.authenticator.manager.'.$id, new ChildDefinition('security.authenticator.manager')) ->replaceArgument(0, $authenticators) @@ -596,7 +611,7 @@ private function createContextListener(ContainerBuilder $container, string $cont return $this->contextListeners[$contextKey] = $listenerId; } - private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint, string $contextListenerId = null) + private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint, string $contextListenerId = null): array { $listeners = []; $entryPoints = []; @@ -611,6 +626,10 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri throw new InvalidConfigurationException(sprintf('Authenticator factory "%s" ("%s") must implement "%s".', get_debug_type($factory), $key, AuthenticatorFactoryInterface::class)); } + if (null === $userProvider && !$factory instanceof StatelessAuthenticatorFactoryInterface) { + $userProvider = $this->createMissingUserProvider($container, $id, $key); + } + $authenticators = $factory->createAuthenticator($container, $id, $firewall[$key], $userProvider); if (\is_array($authenticators)) { foreach ($authenticators as $authenticator) { @@ -637,7 +656,7 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri return [$listeners, $defaultEntryPoint]; } - private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): string + private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): ?string { if (isset($firewall[$factoryKey]['provider'])) { if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$factoryKey]['provider'])])) { @@ -656,13 +675,11 @@ private function getUserProvider(ContainerBuilder $container, string $id, array } if (!$providerIds) { - $userProvider = sprintf('security.user.provider.missing.%s', $factoryKey); - $container->setDefinition( - $userProvider, - (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id) - ); + if ($firewall['stateless'] ?? false) { + return null; + } - return $userProvider; + return $this->createMissingUserProvider($container, $id, $factoryKey); } if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey || 'custom_authenticators' === $factoryKey) { @@ -676,7 +693,18 @@ private function getUserProvider(ContainerBuilder $container, string $id, array throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" authenticator on "%s" firewall is ambiguous as there is more than one registered provider.', $factoryKey, $id)); } - private function createHashers(array $hashers, ContainerBuilder $container) + private function createMissingUserProvider(ContainerBuilder $container, string $id, string $factoryKey): string + { + $userProvider = sprintf('security.user.provider.missing.%s', $factoryKey); + $container->setDefinition( + $userProvider, + (new ChildDefinition('security.user.provider.missing'))->replaceArgument(0, $id) + ); + + return $userProvider; + } + + private function createHashers(array $hashers, ContainerBuilder $container): void { $hasherMap = []; foreach ($hashers as $class => $hasher) { @@ -689,7 +717,12 @@ private function createHashers(array $hashers, ContainerBuilder $container) ; } - private function createHasher(array $config) + /** + * @param array $config + * + * @return Reference|array + */ + private function createHasher(array $config): Reference|array { // a custom hasher service if (isset($config['id'])) { @@ -980,13 +1013,13 @@ private function createRequestMatcher(ContainerBuilder $container, string $path return $this->requestMatchers[$id] = new Reference($id); } - public function addAuthenticatorFactory(AuthenticatorFactoryInterface $factory) + public function addAuthenticatorFactory(AuthenticatorFactoryInterface $factory): void { $this->factories[] = [$factory->getPriority(), $factory]; $this->sortedFactories = []; } - public function addUserProviderFactory(UserProviderFactoryInterface $factory) + public function addUserProviderFactory(UserProviderFactoryInterface $factory): void { $this->userProviderFactories[] = $factory; } @@ -1009,9 +1042,7 @@ public function getConfiguration(array $config, ContainerBuilder $container): ?C private function isValidIps(string|array $ips): bool { - $ipsList = array_reduce((array) $ips, static function (array $ips, string $ip) { - return array_merge($ips, preg_split('/\s*,\s*/', $ip)); - }, []); + $ipsList = array_reduce((array) $ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []); if (!$ipsList) { return false; @@ -1063,9 +1094,7 @@ private function getSortedFactories(): array $factories[] = array_merge($factory, [$i]); } - usort($factories, function ($a, $b) { - return $b[0] <=> $a[0] ?: $a[2] <=> $b[2]; - }); + usort($factories, fn ($a, $b) => $b[0] <=> $a[0] ?: $a[2] <=> $b[2]); $this->sortedFactories = array_column($factories, 1); } diff --git a/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php b/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php index 9e91f3930ab08..0c703f79cfb43 100644 --- a/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php +++ b/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php @@ -36,6 +36,9 @@ public function __construct(FirewallMapInterface $map, EventDispatcherInterface parent::__construct($map, $dispatcher); } + /** + * @return void + */ public function configureLogoutUrlGenerator(RequestEvent $event) { if (!$event->isMainRequest()) { @@ -47,6 +50,9 @@ public function configureLogoutUrlGenerator(RequestEvent $event) } } + /** + * @return void + */ public function onKernelFinishRequest(FinishRequestEvent $event) { if ($event->isMainRequest()) { diff --git a/src/Symfony/Bundle/SecurityBundle/EventListener/VoteListener.php b/src/Symfony/Bundle/SecurityBundle/EventListener/VoteListener.php index ef715f5271a34..34ca91c3a7735 100644 --- a/src/Symfony/Bundle/SecurityBundle/EventListener/VoteListener.php +++ b/src/Symfony/Bundle/SecurityBundle/EventListener/VoteListener.php @@ -31,10 +31,7 @@ public function __construct(TraceableAccessDecisionManager $traceableAccessDecis $this->traceableAccessDecisionManager = $traceableAccessDecisionManager; } - /** - * Event dispatched by a voter during access manager decision. - */ - public function onVoterVote(VoteEvent $event) + public function onVoterVote(VoteEvent $event): void { $this->traceableAccessDecisionManager->addVoterVote($event->getVoter(), $event->getAttributes(), $event->getVote()); } diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd b/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd index 33140fdae8d11..f0835052a6848 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd @@ -171,9 +171,10 @@ - + - + + @@ -407,4 +408,14 @@ + + + + + + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php index 5f4e693b85cbb..27cc0ce51e9c3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php @@ -42,6 +42,7 @@ use Symfony\Component\Security\Core\User\MissingUserProvider; use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; +use Symfony\Component\Security\Http\Controller\SecurityTokenValueResolver; use Symfony\Component\Security\Http\Controller\UserValueResolver; use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener; use Symfony\Component\Security\Http\Firewall; @@ -100,7 +101,13 @@ ->args([ service('security.token_storage'), ]) - ->tag('controller.argument_value_resolver', ['priority' => 120]) + ->tag('controller.argument_value_resolver', ['priority' => 120, 'name' => UserValueResolver::class]) + + ->set('security.security_token_value_resolver', SecurityTokenValueResolver::class) + ->args([ + service('security.token_storage'), + ]) + ->tag('controller.argument_value_resolver', ['priority' => 120, 'name' => SecurityTokenValueResolver::class]) // Authentication related services ->set('security.authentication.trust_resolver', AuthenticationTrustResolver::class) diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php index 58be697595d42..92c91e989779c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php @@ -149,6 +149,7 @@ abstract_arg('user key'), abstract_arg('credentials key'), service('logger')->nullOnInvalid(), + abstract_arg('credentials user identifier'), ]) ->tag('monolog.logger', ['channel' => 'security']) diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php index f1aea7cb2c3d1..fafe477d5bd23 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php @@ -14,6 +14,8 @@ use Symfony\Component\Security\Http\AccessToken\ChainAccessTokenExtractor; use Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor; use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor; +use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler; +use Symfony\Component\Security\Http\AccessToken\Oidc\OidcUserInfoTokenHandler; use Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor; use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator; @@ -40,5 +42,24 @@ ->args([ abstract_arg('access token extractors'), ]) + + // OIDC + ->set('security.access_token_handler.oidc_user_info', OidcUserInfoTokenHandler::class) + ->abstract() + ->args([ + abstract_arg('http client'), + service('logger')->nullOnInvalid(), + 'sub', + ]) + + ->set('security.access_token_handler.oidc', OidcTokenHandler::class) + ->abstract() + ->args([ + abstract_arg('signature algorithm'), + abstract_arg('jwk'), + service('logger')->nullOnInvalid(), + 'sub', + null, + ]) ; }; diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_remember_me.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_remember_me.php index 8304ed9b832da..b861d0de4199e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_remember_me.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_remember_me.php @@ -50,7 +50,6 @@ ->abstract() ->args([ abstract_arg('token provider'), - param('kernel.secret'), abstract_arg('user provider'), service('request_stack'), abstract_arg('options'), diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php index 921aa90b8d730..952b1d75625ad 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php @@ -17,6 +17,7 @@ use Symfony\Component\Security\Http\Authentication\CustomAuthenticationSuccessHandler; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; +use Symfony\Component\Security\Http\EventListener\ClearSiteDataLogoutListener; use Symfony\Component\Security\Http\EventListener\CookieClearingLogoutListener; use Symfony\Component\Security\Http\EventListener\DefaultLogoutListener; use Symfony\Component\Security\Http\EventListener\SessionLogoutListener; @@ -64,6 +65,9 @@ ->set('security.logout.listener.session', SessionLogoutListener::class) ->abstract() + ->set('security.logout.listener.clear_site_data', ClearSiteDataLogoutListener::class) + ->abstract() + ->set('security.logout.listener.cookie_clearing', CookieClearingLogoutListener::class) ->abstract() diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg index b11d1a4637476..10cc2434c2f87 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg +++ b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg @@ -1,4 +1,5 @@ - + + Security diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig index 4a5fa01a9e3bc..48e6c95998c7a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig @@ -2,6 +2,35 @@ {% block page_title 'Security' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% if collector.firewall %} {% set icon %} diff --git a/src/Symfony/Bundle/SecurityBundle/Security.php b/src/Symfony/Bundle/SecurityBundle/Security.php index 94e0e05e183af..d5cd800e020a8 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security.php +++ b/src/Symfony/Bundle/SecurityBundle/Security.php @@ -56,8 +56,10 @@ public function getFirewallConfig(Request $request): ?FirewallConfig * @param UserInterface $user The user to authenticate * @param string|null $authenticatorName The authenticator name (e.g. "form_login") or service id (e.g. SomeApiKeyAuthenticator::class) - required only if multiple authenticators are configured * @param string|null $firewallName The firewall name - required only if multiple firewalls are configured + * + * @return Response|null The authenticator success response if any */ - public function login(UserInterface $user, string $authenticatorName = null, string $firewallName = null): void + public function login(UserInterface $user, string $authenticatorName = null, string $firewallName = null): ?Response { $request = $this->container->get('request_stack')->getCurrentRequest(); $firewallName ??= $this->getFirewallConfig($request)?->getName(); @@ -69,7 +71,8 @@ public function login(UserInterface $user, string $authenticatorName = null, str $authenticator = $this->getAuthenticator($authenticatorName, $firewallName); $this->container->get('security.user_checker')->checkPreAuth($user); - $this->container->get('security.authenticator.managers_locator')->get($firewallName)->authenticateUser($user, $authenticator, $request); + + return $this->container->get('security.authenticator.managers_locator')->get($firewallName)->authenticateUser($user, $authenticator, $request); } /** diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php index a81f6d8983113..5077c6768d95e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php @@ -38,6 +38,9 @@ public function __construct(iterable $listeners, ExceptionListener $exceptionLis $this->config = $config; } + /** + * @return FirewallConfig|null + */ public function getConfig() { return $this->config; @@ -51,11 +54,17 @@ public function getListeners(): iterable return $this->listeners; } + /** + * @return ExceptionListener|null + */ public function getExceptionListener() { return $this->exceptionListener; } + /** + * @return LogoutListener|null + */ public function getLogoutListener() { return $this->logoutListener; diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php index 21e5b8aa68279..6f1bdfcdd4892 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php @@ -72,7 +72,14 @@ private function getFirewallContext(Request $request): ?FirewallContext if (null === $requestMatcher || $requestMatcher->matches($request)) { $request->attributes->set('_firewall_context', $contextId); - return $this->container->get($contextId); + /** @var FirewallContext $context */ + $context = $this->container->get($contextId); + + if ($context->getConfig()?->isStateless() && !$request->attributes->has('_stateless')) { + $request->attributes->set('_stateless', true); + } + + return $context; } } diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 3d90b1690a589..2cbca705f93c1 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -15,6 +15,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\CleanRememberMeVerifierPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\MakeFirewallsEventDispatcherTraceablePass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfFeaturesPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterEntryPointPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterGlobalSecurityEventListenersPass; @@ -22,6 +23,9 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\ReplaceDecoratedRememberMeHandlerPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\SortFirewallListenersPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcTokenHandlerFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcUserInfoTokenHandlerFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\ServiceTokenHandlerFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AccessTokenFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\CustomAuthenticatorFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory; @@ -52,6 +56,9 @@ */ class SecurityBundle extends Bundle { + /** + * @return void + */ public function build(ContainerBuilder $container) { parent::build($container); @@ -70,7 +77,11 @@ public function build(ContainerBuilder $container) $extension->addAuthenticatorFactory(new CustomAuthenticatorFactory()); $extension->addAuthenticatorFactory(new LoginThrottlingFactory()); $extension->addAuthenticatorFactory(new LoginLinkFactory()); - $extension->addAuthenticatorFactory(new AccessTokenFactory()); + $extension->addAuthenticatorFactory(new AccessTokenFactory([ + new ServiceTokenHandlerFactory(), + new OidcUserInfoTokenHandlerFactory(), + new OidcTokenHandlerFactory(), + ])); $extension->addUserProviderFactory(new InMemoryFactory()); $extension->addUserProviderFactory(new LdapFactory()); @@ -92,5 +103,8 @@ public function build(ContainerBuilder $container) AuthenticationEvents::ALIASES, SecurityEvents::ALIASES ))); + + // must be registered before DecoratorServicePass + $container->addCompilerPass(new MakeFirewallsEventDispatcherTraceablePass(), PassConfig::TYPE_OPTIMIZE, 10); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index abc2718865467..9d2b056385de3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -91,7 +91,7 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm $this->assertFalse($collector->isImpersonated()); $this->assertNull($collector->getImpersonatorUser()); $this->assertNull($collector->getImpersonationExitPath()); - $this->assertSame('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $collector->getTokenClass()->getValue()); + $this->assertSame(UsernamePasswordToken::class, $collector->getTokenClass()->getValue()); $this->assertTrue($collector->supportsRoleHierarchy()); $this->assertSame($normalizedRoles, $collector->getRoles()->getValue(true)); $this->assertSame($inheritedRoles, $collector->getInheritedRoles()->getValue(true)); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php index 6e79f8fc644d1..34dc4039d5655 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php @@ -24,11 +24,11 @@ public function testProcessForSecurity() $container = new ContainerBuilder(); $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); - $definition = new Definition('\stdClass'); + $definition = new Definition(\stdClass::class); $definition->addTag('security.expression_language_provider'); $container->setDefinition('some_security_provider', $definition->setPublic(true)); - $container->register('security.expression_language', '\stdClass')->setPublic(true); + $container->register('security.expression_language', \stdClass::class)->setPublic(true); $container->compile(); $calls = $container->getDefinition('security.expression_language')->getMethodCalls(); @@ -42,11 +42,11 @@ public function testProcessForSecurityAlias() $container = new ContainerBuilder(); $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); - $definition = new Definition('\stdClass'); + $definition = new Definition(\stdClass::class); $definition->addTag('security.expression_language_provider'); $container->setDefinition('some_security_provider', $definition->setPublic(true)); - $container->register('my_security.expression_language', '\stdClass')->setPublic(true); + $container->register('my_security.expression_language', \stdClass::class)->setPublic(true); $container->setAlias('security.expression_language', 'my_security.expression_language'); $container->compile(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePassTest.php new file mode 100644 index 0000000000000..e156a2f6f51d4 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/MakeFirewallsEventDispatcherTraceablePassTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Stopwatch\Stopwatch; + +class MakeFirewallsEventDispatcherTraceablePassTest extends TestCase +{ + private $container; + + protected function setUp(): void + { + $this->container = new ContainerBuilder(); + $this->container->register('request_stack', \stdClass::class); + $this->container->register('event_dispatcher', EventDispatcher::class); + $this->container->register('debug.stopwatch', Stopwatch::class); + + $this->container->registerExtension(new SecurityExtension()); + $this->container->loadFromExtension('security', [ + 'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true]], + ]); + + $this->container->addCompilerPass(new DecoratorServicePass(), PassConfig::TYPE_OPTIMIZE); + $this->container->getCompilerPassConfig()->setRemovingPasses([]); + $this->container->getCompilerPassConfig()->setAfterRemovingPasses([]); + + $securityBundle = new SecurityBundle(); + $securityBundle->build($this->container); + } + + public function testEventDispatcherIsDecoratedOnDebugMode() + { + $this->container->setParameter('kernel.debug', true); + + $this->container->compile(); + + $dispatcherDefinition = $this->container->findDefinition('security.event_dispatcher.main'); + + $this->assertSame(TraceableEventDispatcher::class, $dispatcherDefinition->getClass()); + $this->assertSame( + [['name' => 'security.event_dispatcher.main']], + $dispatcherDefinition->getTag('event_dispatcher.dispatcher') + ); + } + + public function testEventDispatcherIsNotDecoratedOnNonDebugMode() + { + $this->container->setParameter('kernel.debug', false); + + $this->container->compile(); + + $dispatcherDefinition = $this->container->findDefinition('security.event_dispatcher.main'); + + $this->assertSame(EventDispatcher::class, $dispatcherDefinition->getClass()); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php index 4ec5149f7a262..c7f437e9d4808 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php @@ -191,10 +191,8 @@ private function assertListeners(array $expectedListeners, string $dispatcherId $actualListeners[] = $arguments; } - $foundListeners = array_uintersect($expectedListeners, $actualListeners, function (array $a, array $b) { - // PHP internally sorts all the arrays first, so returning proper 1 / -1 values is crucial - return $a <=> $b; - }); + // PHP internally sorts all the arrays first, so returning proper 1 / -1 values is crucial + $foundListeners = array_uintersect($expectedListeners, $actualListeners, fn (array $a, array $b) => $a <=> $b); $this->assertEquals($expectedListeners, $foundListeners); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/SortFirewallListenersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/SortFirewallListenersPassTest.php index 8cbf745e9cc88..dde5127bbd7f9 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/SortFirewallListenersPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/SortFirewallListenersPassTest.php @@ -65,7 +65,7 @@ public function supports(Request $request): ?bool { } - public function authenticate(RequestEvent $event) + public function authenticate(RequestEvent $event): void { } @@ -81,7 +81,7 @@ public function supports(Request $request): ?bool { } - public function authenticate(RequestEvent $event) + public function authenticate(RequestEvent $event): void { } @@ -97,7 +97,7 @@ public function supports(Request $request): ?bool { } - public function authenticate(RequestEvent $event) + public function authenticate(RequestEvent $event): void { } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTestCase.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTestCase.php index 4181bac58323f..44193e4ec0a58 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTestCase.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTestCase.php @@ -30,6 +30,7 @@ use Symfony\Component\PasswordHasher\Hasher\SodiumPasswordHasher; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy; +use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticatorManager; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; @@ -92,7 +93,7 @@ public function testUserProviders() { $container = $this->getContainer('container1'); - $providers = array_values(array_filter($container->getServiceIds(), function ($key) { return str_starts_with($key, 'security.user.provider.concrete'); })); + $providers = array_values(array_filter($container->getServiceIds(), fn ($key) => str_starts_with($key, 'security.user.provider.concrete'))); $expectedProviders = [ 'security.user.provider.concrete.default', @@ -180,6 +181,7 @@ public function testFirewalls() 'invalidate_session' => true, 'delete_cookies' => [], 'enable_csrf' => null, + 'clear_site_data' => [], ], ], [ @@ -240,7 +242,7 @@ public function testFirewalls() ], ], $listeners); - $this->assertFalse($container->hasAlias('Symfony\Component\Security\Core\User\UserCheckerInterface', 'No user checker alias is registered when custom user checker services are registered')); + $this->assertFalse($container->hasAlias(UserCheckerInterface::class, 'No user checker alias is registered when custom user checker services are registered')); } public function testFirewallRequestMatchers() @@ -280,8 +282,8 @@ public function testUserCheckerAliasIsRegistered() { $container = $this->getContainer('no_custom_user_checker'); - $this->assertTrue($container->hasAlias('Symfony\Component\Security\Core\User\UserCheckerInterface', 'Alias for user checker is registered when no custom user checker service is registered')); - $this->assertFalse($container->getAlias('Symfony\Component\Security\Core\User\UserCheckerInterface')->isPublic()); + $this->assertTrue($container->hasAlias(UserCheckerInterface::class, 'Alias for user checker is registered when no custom user checker service is registered')); + $this->assertFalse($container->getAlias(UserCheckerInterface::class)->isPublic()); } public function testAccess() @@ -707,6 +709,13 @@ public function testFirewallListenerWithProvider() $this->addToAssertionCount(1); } + public function testFirewallLogoutClearSiteData() + { + $container = $this->getContainer('logout_clear_site_data'); + $ClearSiteDataConfig = $container->getDefinition('security.firewall.map.config.main')->getArgument(12)['clear_site_data']; + $this->assertSame(['cookies', 'executionContexts'], $ClearSiteDataConfig); + } + protected function getContainer($file) { $file .= '.'.$this->getFileExtension(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/UserProvider/DummyProvider.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/UserProvider/DummyProvider.php index 55d3d6e9a2f10..ffefb8dbdb2fa 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/UserProvider/DummyProvider.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/UserProvider/DummyProvider.php @@ -8,16 +8,16 @@ class DummyProvider implements UserProviderFactoryInterface { - public function create(ContainerBuilder $container, $id, $config) + public function create(ContainerBuilder $container, $id, $config): void { } - public function getKey() + public function getKey(): string { return 'foo'; } - public function addConfiguration(NodeDefinition $node) + public function addConfiguration(NodeDefinition $node): void { } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/logout_clear_site_data.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/logout_clear_site_data.php new file mode 100644 index 0000000000000..3d02a68bb83df --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/logout_clear_site_data.php @@ -0,0 +1,20 @@ +loadFromExtension('security', [ + 'providers' => [ + 'default' => ['id' => 'foo'], + ], + + 'firewalls' => [ + 'main' => [ + 'provider' => 'default', + 'form_login' => true, + 'logout' => [ + 'clear-site-data' => [ + 'cookies', + 'executionContexts', + ], + ], + ], + ], +]); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/logout_clear_site_data.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/logout_clear_site_data.xml new file mode 100644 index 0000000000000..e0eec6eb46d58 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/logout_clear_site_data.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + cookies + executionContexts + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/logout_clear_site_data.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/logout_clear_site_data.yml new file mode 100644 index 0000000000000..f5e6b83436d63 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/logout_clear_site_data.yml @@ -0,0 +1,13 @@ +security: + providers: + default: + id: foo + + firewalls: + main: + provider: default + form_login: true + logout: + clear_site_data: + - cookies + - executionContexts diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php index 20bc11269fa6f..9765ff28338f1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php @@ -71,7 +71,7 @@ public function testCsrfAliases() 'firewalls' => [ 'stub' => [ 'logout' => [ - 'csrf_token_generator' => 'a_token_generator', + 'csrf_token_manager' => 'a_token_manager', 'csrf_token_id' => 'a_token_id', ], ], @@ -82,8 +82,8 @@ public function testCsrfAliases() $processor = new Processor(); $configuration = new MainConfiguration([], []); $processedConfig = $processor->processConfiguration($configuration, [$config]); - $this->assertArrayHasKey('csrf_token_generator', $processedConfig['firewalls']['stub']['logout']); - $this->assertEquals('a_token_generator', $processedConfig['firewalls']['stub']['logout']['csrf_token_generator']); + $this->assertArrayHasKey('csrf_token_manager', $processedConfig['firewalls']['stub']['logout']); + $this->assertEquals('a_token_manager', $processedConfig['firewalls']['stub']['logout']['csrf_token_manager']); $this->assertArrayHasKey('csrf_token_id', $processedConfig['firewalls']['stub']['logout']); $this->assertEquals('a_token_id', $processedConfig['firewalls']['stub']['logout']['csrf_token_id']); } @@ -92,13 +92,13 @@ public function testLogoutCsrf() { $config = [ 'firewalls' => [ - 'custom_token_generator' => [ + 'custom_token_manager' => [ 'logout' => [ - 'csrf_token_generator' => 'a_token_generator', + 'csrf_token_manager' => 'a_token_manager', 'csrf_token_id' => 'a_token_id', ], ], - 'default_token_generator' => [ + 'default_token_manager' => [ 'logout' => [ 'enable_csrf' => true, 'csrf_token_id' => 'a_token_id', @@ -121,18 +121,18 @@ public function testLogoutCsrf() $processedConfig = $processor->processConfiguration($configuration, [$config]); $assertions = [ - 'custom_token_generator' => [true, 'a_token_generator'], - 'default_token_generator' => [true, 'security.csrf.token_manager'], + 'custom_token_manager' => [true, 'a_token_manager'], + 'default_token_manager' => [true, 'security.csrf.token_manager'], 'disabled_csrf' => [false, null], 'empty' => [false, null], ]; - foreach ($assertions as $firewallName => [$enabled, $tokenGenerator]) { + foreach ($assertions as $firewallName => [$enabled, $tokenManager]) { $this->assertEquals($enabled, $processedConfig['firewalls'][$firewallName]['logout']['enable_csrf']); - if ($tokenGenerator) { - $this->assertEquals($tokenGenerator, $processedConfig['firewalls'][$firewallName]['logout']['csrf_token_generator']); + if ($tokenManager) { + $this->assertEquals($tokenManager, $processedConfig['firewalls'][$firewallName]['logout']['csrf_token_manager']); $this->assertEquals('a_token_id', $processedConfig['firewalls'][$firewallName]['logout']['csrf_token_id']); } else { - $this->assertArrayNotHasKey('csrf_token_generator', $processedConfig['firewalls'][$firewallName]['logout']); + $this->assertArrayNotHasKey('csrf_token_manager', $processedConfig['firewalls'][$firewallName]['logout']); } } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php index f31798113b75c..a9da80fbb40ba 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php @@ -12,6 +12,9 @@ namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory; use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcTokenHandlerFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\OidcUserInfoTokenHandlerFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\AccessToken\ServiceTokenHandlerFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AccessTokenFactory; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; @@ -29,7 +32,7 @@ public function testBasicServiceConfiguration() 'token_extractors' => ['BAR', 'FOO'], ]; - $factory = new AccessTokenFactory(); + $factory = new AccessTokenFactory($this->createTokenHandlerFactories()); $finalizedConfig = $this->processConfig($config, $factory); $factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider'); @@ -37,19 +40,99 @@ public function testBasicServiceConfiguration() $this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1')); } - public function testDefaultServiceConfiguration() + public function testDefaultTokenHandlerConfiguration() { $container = new ContainerBuilder(); $config = [ 'token_handler' => 'in_memory_token_handler_service_id', ]; - $factory = new AccessTokenFactory(); + $factory = new AccessTokenFactory($this->createTokenHandlerFactories()); $finalizedConfig = $this->processConfig($config, $factory); $factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider'); $this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1')); + $this->assertTrue($container->hasDefinition('security.access_token_handler.firewall1')); + } + + public function testIdTokenHandlerConfiguration() + { + $container = new ContainerBuilder(); + $config = [ + 'token_handler' => ['id' => 'in_memory_token_handler_service_id'], + ]; + + $factory = new AccessTokenFactory($this->createTokenHandlerFactories()); + $finalizedConfig = $this->processConfig($config, $factory); + + $factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider'); + + $this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1')); + $this->assertTrue($container->hasDefinition('security.access_token_handler.firewall1')); + } + + public function testOidcUserInfoTokenHandlerConfigurationWithExistingClient() + { + $container = new ContainerBuilder(); + $config = [ + 'token_handler' => ['oidc_user_info' => ['client' => 'oidc.client']], + ]; + + $factory = new AccessTokenFactory($this->createTokenHandlerFactories()); + $finalizedConfig = $this->processConfig($config, $factory); + + $factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider'); + + $this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1')); + $this->assertTrue($container->hasDefinition('security.access_token_handler.firewall1')); + $this->assertFalse($container->hasDefinition('http_client.security.access_token_handler.oidc_user_info')); + } + + public function testOidcUserInfoTokenHandlerConfigurationWithClientCreation() + { + $container = new ContainerBuilder(); + $config = [ + 'token_handler' => ['oidc_user_info' => ['client' => ['base_uri' => 'https://www.example.com/realms/demo/protocol/openid-connect/userinfo']]], + ]; + + $factory = new AccessTokenFactory($this->createTokenHandlerFactories()); + $finalizedConfig = $this->processConfig($config, $factory); + + $factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider'); + + $this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1')); + $this->assertTrue($container->hasDefinition('security.access_token_handler.firewall1')); + $this->assertTrue($container->hasDefinition('http_client.security.access_token_handler.oidc_user_info')); + } + + public function testMultipleTokenHandlersSet() + { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('You cannot configure multiple token handlers.'); + + $config = [ + 'token_handler' => [ + 'id' => 'in_memory_token_handler_service_id', + 'oidc_user_info' => ['client' => 'oidc.client'], + ], + ]; + + $factory = new AccessTokenFactory($this->createTokenHandlerFactories()); + $this->processConfig($config, $factory); + } + + public function testNoTokenHandlerSet() + { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('You must set a token handler.'); + + $config = [ + 'token_handler' => [], + ]; + + $factory = new AccessTokenFactory($this->createTokenHandlerFactories()); + $this->processConfig($config, $factory); } public function testNoExtractorsDefined() @@ -63,7 +146,7 @@ public function testNoExtractorsDefined() 'token_extractors' => [], ]; - $factory = new AccessTokenFactory(); + $factory = new AccessTokenFactory($this->createTokenHandlerFactories()); $this->processConfig($config, $factory); } @@ -76,7 +159,7 @@ public function testNoHandlerDefined() 'failure_handler' => 'failure_handler_service_id', ]; - $factory = new AccessTokenFactory(); + $factory = new AccessTokenFactory($this->createTokenHandlerFactories()); $this->processConfig($config, $factory); } @@ -90,4 +173,13 @@ private function processConfig(array $config, AccessTokenFactory $factory) return $node->finalize($normalizedConfig); } + + private function createTokenHandlerFactories(): array + { + return [ + new ServiceTokenHandlerFactory(), + new OidcUserInfoTokenHandlerFactory(), + new OidcTokenHandlerFactory(), + ]; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index faa31b5f27c2d..4c8c16e6a3245 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -193,7 +193,7 @@ public function testPerListenerProviderWithRememberMeAndAnonymous() 'firewalls' => [ 'default' => [ 'form_login' => ['provider' => 'second'], - 'remember_me' => ['secret' => 'baz'], + 'remember_me' => [], ], ], ]); @@ -535,7 +535,7 @@ public function testCustomRememberMeHandler() $container->loadFromExtension('security', [ 'firewalls' => [ 'default' => [ - 'remember_me' => ['secret' => 'very', 'service' => 'custom_remember_me'], + 'remember_me' => ['service' => 'custom_remember_me'], ], ], ]); @@ -566,25 +566,6 @@ public function testSecretRememberMeHasher() $this->assertSame('very', $handler->getArgument(2)); } - public function testSecretRememberMeHandler() - { - $container = $this->getRawContainer(); - - $container->register('custom_remember_me', \stdClass::class); - $container->loadFromExtension('security', [ - 'firewalls' => [ - 'default' => [ - 'remember_me' => ['secret' => 'very', 'token_provider' => 'token_provider_id'], - ], - ], - ]); - - $container->compile(); - - $handler = $container->getDefinition('security.authenticator.remember_me_handler.default'); - $this->assertSame('very', $handler->getArgument(1)); - } - public function sessionConfigurationProvider() { return [ @@ -848,6 +829,64 @@ public function testConfigureCustomFirewallListener() $this->assertContains('custom_firewall_listener_id', $firewallListeners); } + public function testClearSiteDataLogoutListenerEnabled() + { + $container = $this->getRawContainer(); + + $firewallId = 'logout_firewall'; + $container->loadFromExtension('security', [ + 'firewalls' => [ + $firewallId => [ + 'logout' => [ + 'clear_site_data' => ['*'], + ], + ], + ], + ]); + + $container->compile(); + + $this->assertTrue($container->has('security.logout.listener.clear_site_data.'.$firewallId)); + $listenerArgument = $container->getDefinition('security.logout.listener.clear_site_data.'.$firewallId)->getArgument(0); + $this->assertSame(['*'], $listenerArgument); + } + + public function testClearSiteDataLogoutListenerDisabled() + { + $container = $this->getRawContainer(); + + $firewallId = 'logout_firewall'; + $container->loadFromExtension('security', [ + 'firewalls' => [ + $firewallId => [ + 'logout' => [ + 'clear_site_data' => [], + ], + ], + ], + ]); + + $container->compile(); + + $this->assertFalse($container->has('security.logout.listener.clear_site_data.'.$firewallId)); + } + + /** + * @group legacy + */ + public function testNothingDoneWithEmptyConfiguration() + { + $container = $this->getRawContainer(); + + $container->loadFromExtension('security'); + + $this->expectDeprecation('Since symfony/security-bundle 6.3: Enabling bundle "Symfony\Bundle\SecurityBundle\SecurityBundle" and not configuring it is deprecated.'); + + $container->compile(); + + $this->assertFalse($container->has('security.authorization_checker')); + } + protected function getRawContainer() { $container = new ContainerBuilder(); @@ -907,11 +946,11 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio class TestUserChecker implements UserCheckerInterface { - public function checkPreAuth(UserInterface $user) + public function checkPreAuth(UserInterface $user): void { } - public function checkPostAuth(UserInterface $user) + public function checkPostAuth(UserInterface $user): void { } } @@ -940,7 +979,7 @@ public function getKey(): string return 'custom_listener'; } - public function addConfiguration(NodeDefinition $builder) + public function addConfiguration(NodeDefinition $builder): void { } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php index 9b79a4cd7a9e9..00c81bc380b2d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; +use Symfony\Bundle\SecurityBundle\Tests\Functional\app\AppKernel; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\KernelInterface; @@ -47,7 +48,7 @@ protected static function getKernelClass(): string { require_once __DIR__.'/app/AppKernel.php'; - return 'Symfony\Bundle\SecurityBundle\Tests\Functional\app\AppKernel'; + return AppKernel::class; } protected static function createKernel(array $options = []): KernelInterface diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php index 91afad756e630..8ca49ccc1430c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php @@ -11,6 +11,11 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; +use Jose\Component\Core\AlgorithmManager; +use Jose\Component\Core\JWK; +use Jose\Component\Signature\Algorithm\ES256; +use Jose\Component\Signature\JWSBuilder; +use Jose\Component\Sig 10000 nature\Serializer\CompactSerializer; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\HttpFoundation\Response; @@ -315,4 +320,55 @@ public static function customQueryAccessTokenFailure(): iterable { yield ['/foo?protection_token=INVALID_ACCESS_TOKEN']; } + + public function testSelfContainedTokens() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_self_contained_token.yml']); + $client->catchExceptions(false); + $client->request('GET', '/foo', [], [], ['HTTP_AUTHORIZATION' => 'Bearer SELF_CONTAINED_ACCESS_TOKEN']); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true)); + } + + /** + * @requires extension openssl + */ + public function testOidcSuccess() + { + $time = time(); + $claims = [ + 'iat' => $time, + 'nbf' => $time, + 'exp' => $time + 3600, + 'iss' => 'https://www.example.com/', + 'aud' => 'Symfony OIDC', + 'sub' => 'e21bf182-1538-406e-8ccb-e25a17aba39f', + 'username' => 'dunglas', + ]; + $token = (new CompactSerializer())->serialize((new JWSBuilder(new AlgorithmManager([ + new ES256(), + ])))->create() + ->withPayload(json_encode($claims)) + // tip: use https://mkjwk.org/ to generate a JWK + ->addSignature(new JWK([ + 'kty' => 'EC', + 'crv' => 'P-256', + 'x' => '0QEAsI1wGI-dmYatdUZoWSRWggLEpyzopuhwk-YUnA4', + 'y' => 'KYl-qyZ26HobuYwlQh-r0iHX61thfP82qqEku7i0woo', + 'd' => 'iA_TV2zvftni_9aFAQwFO_9aypfJFCSpcCyevDvz220', + ]), ['alg' => 'ES256']) + ->build() + ); + + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_oidc.yml']); + $client->request('GET', '/foo', [], [], ['HTTP_AUTHORIZATION' => sprintf('Bearer %s', $token)]); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true)); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php index 4f94cc6936a05..8e6c7b6dd44d0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php @@ -12,19 +12,17 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler; use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; class AccessTokenHandler implements AccessTokenHandlerInterface { - public function __construct() - { - } - public function getUserBadgeFrom(string $accessToken): UserBadge { return match ($accessToken) { 'VALID_ACCESS_TOKEN' => new UserBadge('dunglas'), + 'SELF_CONTAINED_ACCESS_TOKEN' => new UserBadge('dunglas', fn () => new InMemoryUser('dunglas', null, ['ROLE_USER'])), default => throw new BadCredentialsException('Invalid credentials.'), }; } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php index dca54cfb0d999..3060f4ef9e8c6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php @@ -46,7 +46,7 @@ public function authenticate(Request $request): Passport $userLoader = null; if ($this->selfLoadingUser) { - $userLoader = function ($username) { return new InMemoryUser($username, 'test', ['ROLE_USER']); }; + $userLoader = fn ($username) => new InMemoryUser($username, 'test', ['ROLE_USER']); } return new SelfValidatingPassport(new UserBadge($email, $userLoader)); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/AuthenticatorBundle.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/AuthenticatorBundle.php index 730974f17f169..6d0f01391ac7e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/AuthenticatorBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/AuthenticatorBundle.php @@ -17,7 +17,7 @@ class AuthenticatorBundle extends Bundle { - public function build(ContainerBuilder $container) + public function build(ContainerBuilder $container): void { parent::build($container); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php index 22f7de69b208c..42a87a1235937 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Controller; use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginType; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AccessDeniedException; @@ -29,7 +30,7 @@ public function __construct(ContainerInterface $container) public function loginAction() { - $form = $this->container->get('form.factory')->create('Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginType'); + $form = $this->container->get('form.factory')->create(UserLoginType::class); return new Response($this->container->get('twig')->render('@CsrfFormLogin/Login/login.html.twig', [ 'form' => $form->createView(), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php index 0f89b3c1b4fc8..5868a0b3a9094 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php @@ -12,6 +12,9 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; @@ -29,19 +32,17 @@ */ class UserLoginType extends AbstractType { - private $requestStack; - - public function __construct(RequestStack $requestStack) - { - $this->requestStack = $requestStack; + public function __construct( + private readonly RequestStack $requestStack, + ) { } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder - ->add('username', 'Symfony\Component\Form\Extension\Core\Type\TextType') - ->add('password', 'Symfony\Component\Form\Extension\Core\Type\PasswordType') - ->add('_target_path', 'Symfony\Component\Form\Extension\Core\Type\HiddenType') + ->add('username', TextType::class) + ->add('password', PasswordType::class) + ->add('_target_path', HiddenType::class) ; $request = $this->requestStack->getCurrentRequest(); @@ -68,7 +69,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) }); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { /* Note: the form's csrf_token_id must correspond to that for the form login * listener in order for the CSRF token to validate successfully. diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/DependencyInjection/FirewallEntryPointExtension.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/DependencyInjection/FirewallEntryPointExtension.php index dfedac3735f53..eb33a4ce8f211 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/DependencyInjection/FirewallEntryPointExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/DependencyInjection/FirewallEntryPointExtension.php @@ -18,7 +18,7 @@ class FirewallEntryPointExtension extends Extension { - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.xml'); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/DependencyInjection/FormLoginExtension.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/DependencyInjection/FormLoginExtension.php index 7cd7ad446f255..0eac0017d0d51 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/DependencyInjection/FormLoginExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/DependencyInjection/FormLoginExtension.php @@ -11,16 +11,17 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\DependencyInjection; +use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\Security\LocalizedFormFailureHandler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Reference; class FormLoginExtension extends Extension { - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $container - ->register('localized_form_failure_handler', 'Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\Security\LocalizedFormFailureHandler') + ->register('localized_form_failure_handler', LocalizedFormFailureHandler::class) ->addArgument(new Reference('router')) ; } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/FormLoginBundle.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/FormLoginBundle.php index 62490a739bdcc..76ac78c3c488c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/FormLoginBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/FormLoginBundle.php @@ -18,7 +18,7 @@ class FormLoginBundle extends Bundle { - public function build(ContainerBuilder $container) + public function build(ContainerBuilder $container): void { parent::build($container); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/StaticTokenProvider.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/StaticTokenProvider.php index 8a669e99bc48d..c79518b6cb9fb 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/StaticTokenProvider.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/StaticTokenProvider.php @@ -39,12 +39,12 @@ public function loadTokenBySeries(string $series): PersistentTokenInterface return $token; } - public function deleteTokenBySeries(string $series) + public function deleteTokenBySeries(string $series): void { unset(self::$db[$series]); } - public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed) + public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed): void { $token = $this->loadTokenBySeries($series); $refl = new \ReflectionClass($token); @@ -57,7 +57,7 @@ public function updateToken(string $series, string $tokenValue, \DateTime $lastU self::$db[$series] = $token; } - public function createNewToken(PersistentTokenInterface $token) + public function createNewToken(PersistentTokenInterface $token): void { self::$db[$token->getSeries()] = $token; } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php index 8336dce245792..8652dc73b41d1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/TestBundle.php @@ -18,7 +18,7 @@ class TestBundle extends Bundle { - public function build(ContainerBuilder $container) + public function build(ContainerBuilder $container): void { $container->setParameter('container.build_hash', 'test_bundle'); $container->setParameter('container.build_time', time()); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_oidc.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_oidc.yml new file mode 100644 index 0000000000000..45802961a1a61 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_oidc.yml @@ -0,0 +1,34 @@ +imports: + - { resource: ./../config/framework.yml } + +framework: + http_method_override: false + serializer: ~ + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + token_handler: + oidc: + claim: 'username' + audience: 'Symfony OIDC' + signature: + algorithm: 'ES256' + # tip: use https://mkjwk.org/ to generate a JWK + key: '{"kty":"EC","d":"iA_TV2zvftni_9aFAQwFO_9aypfJFCSpcCyevDvz220","crv":"P-256","x":"0QEAsI1wGI-dmYatdUZoWSRWggLEpyzopuhwk-YUnA4","y":"KYl-qyZ26HobuYwlQh-r0iHX61thfP82qqEku7i0woo"}' + token_extractors: 'header' + realm: 'My API' + + access_control: + - { path: ^/foo, roles: ROLE_USER } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_self_contained_token.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_self_contained_token.yml new file mode 100644 index 0000000000000..8143698fdec1a --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_self_contained_token.yml @@ -0,0 +1,26 @@ +imports: + - { resource: ./../config/framework.yml } + +framework: + http_method_override: false + serializer: ~ + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + firewalls: + main: + pattern: ^/ + stateless: true + access_token: + token_handler: access_token.access_token_handler + token_extractors: 'header' + realm: 'My API' + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml index bdd94fd0afaa7..290804e61cbe6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AliasedEvents/config.yml @@ -1,2 +1,10 @@ imports: - { resource: ./../config/framework.yml } + +security: + providers: + dummy: + memory: ~ + firewalls: + dummy: + security: false diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php index d10326e676a38..4969b13419fb8 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php @@ -75,7 +75,7 @@ public function getLogDir(): string return sys_get_temp_dir().'/'.$this->varDir.'/'.$this->testCase.'/logs'; } - public function registerContainerConfiguration(LoaderInterface $loader) + public function registerContainerConfiguration(LoaderInterface $loader): void { foreach ($this->rootConfig as $config) { $loader->load($config); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml index 069fece61756f..9f84b66ee67c1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml @@ -43,7 +43,7 @@ security: logout: path: /logout_path target: / - csrf_token_generator: security.csrf.token_manager + csrf_token_manager: security.csrf.token_manager access_control: - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php index 0b466d0af7990..92703f41ec3c7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/LoginLink/FirewallAwareLoginLinkHandlerTest.php @@ -69,14 +69,10 @@ private function createLocator(array $linkers) $locator = $this->createMock(ContainerInterface::class); $locator->expects($this->any()) ->method('has') - ->willReturnCallback(function ($firewallName) use ($linkers) { - return isset($linkers[$firewallName]); - }); + ->willReturnCallback(fn ($firewallName) => isset($linkers[$firewallName])); $locator->expects($this->any()) ->method('get') - ->willReturnCallback(function ($firewallName) use ($linkers) { - return $linkers[$firewallName]; - }); + ->willReturnCallback(fn ($firewallName) => $linkers[$firewallName]); return $locator; } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php index d174e13b5cff8..fdf9c3d53a3c7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php @@ -54,16 +54,16 @@ public function testGetListenersWithInvalidParameter() $this->assertEquals([[], null, null], $firewallMap->getListeners($request)); $this->assertNull($firewallMap->getFirewallConfig($request)); $this->assertFalse($request->attributes->has(self::ATTRIBUTE_FIREWALL_CONTEXT)); + $this->assertFalse($request->attributes->has('_stateless')); } - public function testGetListeners() + /** @dataProvider providesStatefulStatelessRequests */ + public function testGetListeners(Request $request, bool $expectedState) { - $request = new Request(); - $firewallContext = $this->createMock(FirewallContext::class); - $firewallConfig = new FirewallConfig('main', 'user_checker'); - $firewallContext->expects($this->once())->method('getConfig')->willReturn($firewallConfig); + $firewallConfig = new FirewallConfig('main', 'user_checker', null, true, true); + $firewallContext->expects($this->exactly(2))->method('getConfig')->willReturn($firewallConfig); $listener = function () {}; $firewallContext->expects($this->once())->method('getListeners')->willReturn([$listener]); @@ -88,5 +88,13 @@ public function testGetListeners() $this->assertEquals([[$listener], $exceptionListener, $logoutListener], $firewallMap->getListeners($request)); $this->assertEquals($firewallConfig, $firewallMap->getFirewallConfig($request)); $this->assertEquals('security.firewall.map.context.foo', $request->attributes->get(self::ATTRIBUTE_FIREWALL_CONTEXT)); + $this->assertEquals($expectedState, $request->attributes->get('_stateless')); + } + + public static function providesStatefulStatelessRequests(): \Generator + { + yield [new Request(), true]; + yield [new Request(attributes: ['_stateless' => false]), false]; + yield [new Request(attributes: ['_stateless' => true]), true]; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php index 8e1e6156d92a2..95b0006f5f896 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php @@ -141,7 +141,7 @@ public function testLogin() ->willReturnMap([ ['request_stack', $requestStack], ['security.firewall.map', $firewallMap], - ['security.authenticator.managers_locator', new ServiceLocator(['main' => fn () => $userAuthenticator])], + ['security.authenticator.managers_locator', $this->createContainer('main', $userAuthenticator)], ['security.user_checker', $userChecker], ]) ; @@ -169,6 +169,57 @@ public function testLogin() $security->login($user); } + public function testLoginReturnsAuthenticatorResponse() + { + $request = new Request(); + $authenticator = $this->createMock(AuthenticatorInterface::class); + $requestStack = $this->createMock(RequestStack::class); + $firewallMap = $this->createMock(FirewallMap::class); + $firewall = new FirewallConfig('main', 'main'); + $user = $this->createMock(UserInterface::class); + $userChecker = $this->createMock(UserCheckerInterface::class); + $userAuthenticator = $this->createMock(UserAuthenticatorInterface::class); + + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['request_stack', $requestStack], + ['security.firewall.map', $firewallMap], + ['security.authenticator.managers_locator', $this->createContainer('main', $userAuthenticator)], + ['security.user_checker', $userChecker], + ]) + ; + + $requestStack->expects($this->once())->method('getCurrentRequest')->willReturn($request); + $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewall); + $userChecker->expects($this->once())->method('checkPreAuth')->with($user); + $userAuthenticator->expects($this->once())->method('authenticateUser') + ->with($user, $authenticator, $request) + ->willReturn(new Response('authenticator response')); + + $firewallAuthenticatorLocator = $this->createMock(ServiceProviderInterface::class); + $firewallAuthenticatorLocator + ->expects($this->once()) + ->method('getProvidedServices') + ->willReturn(['security.authenticator.custom.dev' => $authenticator]) + ; + $firewallAuthenticatorLocator + ->expects($this->once()) + ->method('get') + ->with('security.authenticator.custom.dev') + ->willReturn($authenticator) + ; + + $security = new Security($container, ['main' => $firewallAuthenticatorLocator]); + + $response = $security->login($user); + + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals('authenticator response', $response->getContent()); + } + public function testLoginWithoutAuthenticatorThrows() { $request = new Request(); diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index cae9c7635a59e..8fb916cd27114 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -27,7 +27,7 @@ "symfony/password-hasher": "^5.4|^6.0", "symfony/security-core": "^6.2", "symfony/security-csrf": "^5.4|^6.0", - "symfony/security-http": "^6.2.10" + "symfony/security-http": "^6.3" }, "require-dev": { "doctrine/annotations": "^1.10.4|^2", @@ -48,7 +48,13 @@ "symfony/twig-bridge": "^5.4|^6.0", "symfony/validator": "^5.4|^6.0", "symfony/yaml": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" + "twig/twig": "^2.13|^3.0.4", + "web-token/jwt-checker": "^3.1", + "web-token/jwt-signature-algorithm-hmac": "^3.1", + "web-token/jwt-signature-algorithm-ecdsa": "^3.1", + "web-token/jwt-signature-algorithm-rsa": "^3.1", + "web-token/jwt-signature-algorithm-eddsa": "^3.1", + "web-token/jwt-signature-algorithm-none": "^3.1" }, "conflict": { "symfony/browser-kit": "<5.4", diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md index 5502812c27ec1..ba5a3f08850d8 100644 --- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate the `Twig_Environment` autowiring alias, use `Twig\Environment` instead + 6.2 --- diff --git a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php index d26ddf358aaba..4e748ddc61228 100644 --- a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php +++ b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php @@ -42,15 +42,9 @@ public function warmUp(string $cacheDir): array { $this->twig ??= $this->container->get('twig'); - $files = []; - foreach ($this->iterator as $template) { try { - $template = $this->twig->load($template); - - if (\is_callable([$template, 'unwrap'])) { - $files[] = (new \ReflectionClass($template->unwrap()))->getFileName(); - } + $this->twig->load($template); } catch (Error) { /* * Problem during compilation, give up for this template (e.g. syntax errors). @@ -63,7 +57,7 @@ public function warmUp(string $cacheDir): array } } - return $files; + return []; } public function isOptional(): bool diff --git a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php index f85dd13aa2f3a..10aa983e0ea46 100644 --- a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php +++ b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php @@ -23,7 +23,7 @@ #[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] final class LintCommand extends BaseLintCommand { - protected function configure() + protected function configure(): void { parent::configure(); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index 6654fe08e11a9..63dd68e91b90d 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -25,6 +25,9 @@ */ class ExtensionPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!class_exists(Packages::class)) { @@ -43,6 +46,12 @@ public function process(ContainerBuilder $container) $container->removeDefinition('twig.extension.yaml'); } + if (!$container->has('asset_mapper')) { + // edge case where AssetMapper is installed, but not enabled + $container->removeDefinition('twig.extension.importmap'); + $container->removeDefinition('twig.runtime.importmap'); + } + $viewDir = \dirname((new \ReflectionClass(\Symfony\Bridge\Twig\Extension\FormExtension::class))->getFileName(), 2).'/Resources/views'; $templateIterator = $container->getDefinition('twig.template_iterator'); $templatePaths = $templateIterator->getArgument(1); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php index 82cf1c145a312..ecb99ce20ea08 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/RuntimeLoaderPass.php @@ -21,6 +21,9 @@ */ class RuntimeLoaderPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('twig.runtime_loader')) { diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php index 3a90bce15ffaa..99b975edea3a0 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php @@ -24,6 +24,9 @@ class TwigEnvironmentPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; + /** + * @return void + */ public function process(ContainerBuilder $container) { if (false === $container->hasDefinition('twig')) { diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php index a422f668257ad..1da7e8679724f 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php @@ -23,6 +23,9 @@ */ class TwigLoaderPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (false === $container->hasDefinition('twig')) { diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index 0655a51e7c159..4712b18a6ac1b 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -33,7 +33,7 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode = $treeBuilder->getRootNode(); $rootNode->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && \array_key_exists('exception_controller', $v); }) + ->ifTrue(fn ($v) => \is_array($v) && \array_key_exists('exception_controller', $v)) ->then(function ($v) { if (isset($v['exception_controller'])) { throw new InvalidConfigurationException('Option "exception_controller" under "twig" must be null or unset, use "error_controller" under "framework" instead.'); @@ -54,7 +54,7 @@ public function getConfigTreeBuilder(): TreeBuilder return $treeBuilder; } - private function addFormThemesSection(ArrayNodeDefinition $rootNode) + private function addFormThemesSection(ArrayNodeDefinition $rootNode): void { $rootNode ->fixXmlConfig('form_theme') @@ -64,17 +64,15 @@ private function addFormThemesSection(ArrayNodeDefinition $rootNode) ->prototype('scalar')->defaultValue('form_div_layout.html.twig')->end() ->example(['@My/form.html.twig']) ->validate() - ->ifTrue(function ($v) { return !\in_array('form_div_layout.html.twig', $v); }) - ->then(function ($v) { - return array_merge(['form_div_layout.html.twig'], $v); - }) + ->ifTrue(fn ($v) => !\in_array('form_div_layout.html.twig', $v)) + ->then(fn ($v) => array_merge(['form_div_layout.html.twig'], $v)) ->end() ->end() ->end() ; } - private function addGlobalsSection(ArrayNodeDefinition $rootNode) + private function addGlobalsSection(ArrayNodeDefinition $rootNode): void { $rootNode ->fixXmlConfig('global') @@ -86,7 +84,7 @@ private function addGlobalsSection(ArrayNodeDefinition $rootNode) ->prototype('array') ->normalizeKeys(false) ->beforeNormalization() - ->ifTrue(function ($v) { return \is_string($v) && str_starts_with($v, '@'); }) + ->ifTrue(fn ($v) => \is_string($v) && str_starts_with($v, '@')) ->then(function ($v) { if (str_starts_with($v, '@@')) { return substr($v, 1); @@ -106,7 +104,7 @@ private function addGlobalsSection(ArrayNodeDefinition $rootNode) return true; }) - ->then(function ($v) { return ['value' => $v]; }) + ->then(fn ($v) => ['value' => $v]) ->end() ->children() ->scalarNode('id')->end() @@ -124,7 +122,7 @@ private function addGlobalsSection(ArrayNodeDefinition $rootNode) ; } - private function addTwigOptions(ArrayNodeDefinition $rootNode) + private function addTwigOptions(ArrayNodeDefinition $rootNode): void { $rootNode ->fixXmlConfig('path') @@ -151,7 +149,7 @@ private function addTwigOptions(ArrayNodeDefinition $rootNode) ->info('Pattern of file name used for cache warmer and linter') ->beforeNormalization() ->ifString() - ->then(function ($value) { return [$value]; }) + ->then(fn ($value) => [$value]) ->end() ->prototype('scalar')->end() ->end() @@ -187,7 +185,7 @@ private function addTwigOptions(ArrayNodeDefinition $rootNode) ; } - private function addTwigFormatOptions(ArrayNodeDefinition $rootNode) + private function addTwigFormatOptions(ArrayNodeDefinition $rootNode): void { $rootNode ->children() @@ -216,7 +214,7 @@ private function addTwigFormatOptions(ArrayNodeDefinition $rootNode) ; } - private function addMailerSection(ArrayNodeDefinition $rootNode) + private function addMailerSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php index 7505c1fcd36fa..b3eec9ff60e34 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php @@ -42,6 +42,9 @@ public function __construct(string $dateFormat, string $intervalFormat, ?string $this->thousandsSeparator = $thousandsSeparator; } + /** + * @return void + */ public function configure(Environment $environment) { $environment->getExtension(CoreExtension::class)->setDateFormat($this->dateFormat, $this->intervalFormat); @@ -53,7 +56,7 @@ public function configure(Environment $environment) $environment->getExtension(CoreExtension::class)->setNumberFormat($this->decimals, $this->decimalPoint, $this->thousandsSeparator); // wrap UndefinedCallableHandler in closures for lazy-autoloading - $environment->registerUndefinedFilterCallback(function ($name) { return UndefinedCallableHandler::onUndefinedFilter($name); }); - $environment->registerUndefinedFunctionCallback(function ($name) { return UndefinedCallableHandler::onUndefinedFunction($name); }); + $environment->registerUndefinedFilterCallback(fn ($name) => UndefinedCallableHandler::onUndefinedFilter($name)); + $environment->registerUndefinedFunctionCallback(fn ($name) => UndefinedCallableHandler::onUndefinedFunction($name)); } } diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index b323bf808946e..f257f60298bb6 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection; +use Symfony\Component\AssetMapper\AssetMapper; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\Console\Application; @@ -35,6 +36,9 @@ */ class TwigExtension extends Extension { + /** + * @return void + */ public function load(array $configs, ContainerBuilder $container) { $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); @@ -83,6 +87,10 @@ public function load(array $configs, ContainerBuilder $container) } } + if ($container::willBeAvailable('symfony/asset-mapper', AssetMapper::class, ['symfony/twig-bundle'])) { + $loader->load('importmap.php'); + } + $container->setParameter('twig.form.resources', $config['form_themes']); $container->setParameter('twig.default_path', $config['default_path']); $defaultTwigPath = $container->getParameterBag()->resolveValue($config['default_path']); diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/importmap.php b/src/Symfony/Bundle/TwigBundle/Resources/config/importmap.php new file mode 100644 index 0000000000000..c28021959e0f1 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/importmap.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Extension\ImportMapExtension; +use Symfony\Bridge\Twig\Extension\ImportMapRuntime; + +return static function (ContainerConfigurator $container) { + $container->services() + + ->set('twig.runtime.importmap', ImportMapRuntime::class) + ->args([service('asset_mapper.importmap.renderer')]) + ->tag('twig.runtime') + + ->set('twig.extension.importmap', ImportMapExtension::class) + ->tag('twig.extension') + ; +}; diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php index aa5c543b30f40..6525d875a5737 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php @@ -68,6 +68,7 @@ ->tag('container.preload', ['class' => TemplateWrapper::class]) ->alias('Twig_Environment', 'twig') + ->deprecate('symfony/twig-bundle', '6.3', 'The "%alias_id%" service alias is deprecated, use "'.Environment::class.'" or "twig" instead.') ->alias(Environment::class, 'twig') ->set('twig.app_variable', AppVariable::class) @@ -75,6 +76,7 @@ ->call('setDebug', [param('kernel.debug')]) ->call('setTokenStorage', [service('security.token_storage')->ignoreOnInvalid()]) ->call('setRequestStack', [service('request_stack')->ignoreOnInvalid()]) + ->call('setLocaleSwitcher', [service('translation.locale_switcher')->ignoreOnInvalid()]) ->set('twig.template_iterator', TemplateIterator::class) ->args([service('kernel'), abstract_arg('Twig paths'), param('twig.default_path'), abstract_arg('File name pattern')]) diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index 70693c0784bb8..e1fcb3af33a92 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -24,7 +24,10 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\Form\FormRenderer; use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Stopwatch\Stopwatch; +use Twig\Environment; class TwigExtensionTest extends TestCase { @@ -35,7 +38,7 @@ public function testLoadEmptyConfiguration() $container->loadFromExtension('twig'); $this->compileContainer($container); - $this->assertEquals('Twig\Environment', $container->getDefinition('twig')->getClass(), '->load() loads the twig.xml file'); + $this->assertEquals(Environment::class, $container->getDefinition('twig')->getClass(), '->load() loads the twig.xml file'); $this->assertContains('form_div_layout.html.twig', $container->getParameter('twig.form.resources'), '->load() includes default template for form resources'); @@ -60,7 +63,7 @@ public function testLoadFullConfiguration($format) $this->loadFromFile($container, 'full', $format); $this->compileContainer($container); - $this->assertEquals('Twig\Environment', $container->getDefinition('twig')->getClass(), '->load() loads the twig.xml file'); + $this->assertEquals(Environment::class, $container->getDefinition('twig')->getClass(), '->load() loads the twig.xml file'); // Form resources $resources = $container->getParameter('twig.form.resources'); @@ -221,7 +224,7 @@ public function testStopwatchExtensionAvailability($debug, $stopwatchEnabled, $e $container = $this->createContainer(); $container->setParameter('kernel.debug', $debug); if ($stopwatchEnabled) { - $container->register('debug.stopwatch', 'Symfony\Component\Stopwatch\Stopwatch'); + $container->register('debug.stopwatch', Stopwatch::class); } $container->registerExtension(new TwigExtension()); $container->loadFromExtension('twig'); @@ -262,9 +265,9 @@ public function testRuntimeLoader() $loader = $container->getDefinition('twig.runtime_loader'); $args = $container->getDefinition((string) $loader->getArgument(0))->getArgument(0); - $this->assertArrayHasKey('Symfony\Component\Form\FormRenderer', $args); + $this->assertArrayHasKey(FormRenderer::class, $args); $this->assertArrayHasKey('FooClass', $args); - $this->assertEquals('twig.form.renderer', $args['Symfony\Component\Form\FormRenderer']->getValues()[0]); + $this->assertEquals('twig.form.renderer', $args[FormRenderer::class]->getValues()[0]); $this->assertEquals('foo', $args['FooClass']->getValues()[0]); } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Functional/EmptyAppTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Functional/EmptyAppTest.php index bfdf9418a765a..6451f6185150c 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Functional/EmptyAppTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Functional/EmptyAppTest.php @@ -58,7 +58,7 @@ public function registerBundles(): iterable return [new TwigBundle()]; } - public function registerContainerConfiguration(LoaderInterface $loader) + public function registerContainerConfiguration(LoaderInterface $loader): void { $loader->load(static function (ContainerBuilder $container) { $container->register('error_renderer.html', HtmlErrorRenderer::class); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php index 691430ead578b..a12a48ef51831 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php @@ -59,7 +59,7 @@ public function registerBundles(): iterable return [new FrameworkBundle(), new TwigBundle()]; } - public function registerContainerConfiguration(LoaderInterface $loader) + public function registerContainerConfiguration(LoaderInterface $loader): void { $loader->load(function (ContainerBuilder $container) { $container diff --git a/src/Symfony/Bundle/TwigBundle/TwigBundle.php b/src/Symfony/Bundle/TwigBundle/TwigBundle.php index 3910dd5e2e389..802cb536d123f 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigBundle.php +++ b/src/Symfony/Bundle/TwigBundle/TwigBundle.php @@ -27,6 +27,9 @@ */ class TwigBundle extends Bundle { + /** + * @return void + */ public function build(ContainerBuilder $container) { parent::build($container); @@ -38,6 +41,9 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new RuntimeLoaderPass(), PassConfig::TYPE_BEFORE_REMOVING); } + /** + * @return void + */ public function registerCommands(Application $application) { // noop diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index 7b4a3d7362852..bdcfc3bdc5d3f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +6.3 +--- + + * Add a "role=img" and an explicit title in the .svg file used by the web debug toolbar + to improve accessibility with screen readers for blind users + * Add a clickable link to the entry view twig file in the toolbar + 6.1 --- diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index 83257e26f8c3a..b9a861df38dd2 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -172,38 +172,18 @@ public function searchBarAction(Request $request): Response $this->cspHandler?->disableCsp(); - if (!$request->hasSession()) { - $ip = - $method = - $statusCode = - $url = - $start = - $end = - $limit = - $token = null; - } else { - $session = $request->getSession(); - - $ip = $request->query->get('ip', $session->get('_profiler_search_ip')); - $method = $request->query->get('method', $session->get('_profiler_search_method')); - $statusCode = $request->query->get('status_code', $session->get('_profiler_search_status_code')); - $url = $request->query->get('url', $session->get('_profiler_search_url')); - $start = $request->query->get('start', $session->get('_profiler_search_start')); - $end = $request->query->get('end', $session->get('_profiler_search_end')); - $limit = $request->query->get('limit', $session->get('_profiler_search_limit')); - $token = $request->query->get('token', $session->get('_profiler_search_token')); - } + $session = $request->hasSession() ? $request->getSession() : null; return new Response( $this->twig->render('@WebProfiler/Profiler/search.html.twig', [ - 'token' => $token, - 'ip' => $ip, - 'method' => $method, - 'status_code' => $statusCode, - 'url' => $url, - 'start' => $start, - 'end' => $end, - 'limit' => $limit, + 'token' => $request->query->get('token', $session?->get('_profiler_search_token')), + 'ip' => $request->query->get('ip', $session?->get('_profiler_search_ip')), + 'method' => $request->query->get('method', $session?->get('_profiler_search_method')), + 'status_code' => $request->query->get('status_code', $session?->get('_profiler_search_status_code')), + 'url' => $request->query->get('url', $session?->get('_profiler_search_url')), + 'start' => $request->query->get('start', $session?->get('_profiler_search_start')), + 'end' => $request->query->get('end', $session?->get('_profiler_search_end')), + 'limit' => $request->query->get('limit', $session?->get('_profiler_search_limit')), 'request' => $request, 'render_hidden_by_default' => false, ]), @@ -372,7 +352,7 @@ protected function getTemplateManager(): TemplateManager return $this->templateManager ??= new TemplateManager($this->profiler, $this->twig, $this->templates); } - private function denyAccessIfProfilerDisabled() + private function denyAccessIfProfilerDisabled(): void { if (null === $this->profiler) { throw new NotFoundHttpException('The profiler must be enabled.'); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php b/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php index 3275ea792672b..4cc0f8db1efe0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php @@ -71,7 +71,7 @@ public function getNonces(Request $request, Response $response): array * * All related headers will be removed. */ - public function disableCsp() + public function disableCsp(): void { $this->cspDisabled = true; } @@ -96,13 +96,13 @@ public function updateResponseHeaders(Request $request, Response $response): arr return $nonces; } - private function cleanHeaders(Response $response) + private function cleanHeaders(Response $response): void { $response->headers->remove('X-SymfonyProfiler-Script-Nonce'); $response->headers->remove('X-SymfonyProfiler-Style-Nonce'); } - private function removeCspHeaders(Response $response) + private function removeCspHeaders(Response $response): void { $response->headers->remove('X-Content-Security-Policy'); $response->headers->remove('Content-Security-Policy'); @@ -180,9 +180,7 @@ private function generateNonce(): string */ private function generateCspHeader(array $directives): string { - return array_reduce(array_keys($directives), function ($res, $name) use ($directives) { - return ('' !== $res ? $res.'; ' : '').sprintf('%s %s', $name, implode(' ', $directives[$name])); - }, ''); + return array_reduce(array_keys($directives), fn ($res, $name) => ('' !== $res ? $res.'; ' : '').sprintf('%s %s', $name, implode(' ', $directives[$name])), ''); } /** @@ -235,7 +233,7 @@ private function hasHashOrNonce(array $directives): bool return false; } - private function getDirectiveFallback(array $directiveSet, string $type) + private function getDirectiveFallback(array $directiveSet, string $type): ?array { if (\in_array($type, ['script-src-elem', 'style-src-elem'], true) || !isset($directiveSet['default-src'])) { // Let the browser fallback on it's own diff --git a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php index 23a69bc76490c..16e6db29eeaff 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php +++ b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php @@ -37,6 +37,8 @@ class WebProfilerExtension extends Extension * Loads the web profiler configuration. * * @param array $configs An array of configuration settings + * + * @return void */ public function load(array $configs, ContainerBuilder $container) { diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index efaa98e0ca50f..c29a389902113 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -73,7 +73,7 @@ public function setMode(int $mode): void $this->mode = $mode; } - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { $response = $event->getResponse(); $request = $event->getRequest(); @@ -134,7 +134,7 @@ public function onKernelResponse(ResponseEvent $event) /** * Injects the web debug toolbar into the given Response. */ - protected function injectToolbar(Response $response, Request $request, array $nonces) + protected function injectToolbar(Response $response, Request $request, array $nonces): void { $content = $response->getContent(); $pos = strripos($content, ''); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig index b0353b87db310..ca51978f13333 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig @@ -1,5 +1,51 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% if 'unknown' == collector.symfonyState %} {% set block_status = '' %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig index 33f33b235963a..d1255b7745f1b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig @@ -1,7 +1,5 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} - {% block menu %} {{ source('@WebProfiler/Icon/event.svg') }} @@ -10,76 +8,85 @@ {% endblock %} {% block panel %} -

Event Dispatcher

- - {% if collector.calledlisteners is empty %} -
-

No events have been recorded. Check that debugging is enabled in the kernel.

-
- {% else %} -
-
-

Called Listeners {{ collector.calledlisteners|length }}

- -
- {{ helper.render_table(collector.calledlisteners) }} -
-
+

Dispatched Events

+
+ {% for dispatcherName, dispatcherData in collector.data %}
-

Not Called Listeners {{ collector.notcalledlisteners|length }}

+

{{ dispatcherName }}

- {% if collector.notcalledlisteners is empty %} -
-

- There are no uncalled listeners. -

-

- All listeners were called for this request or an error occurred - when trying to collect uncalled listeners (in which case check the - logs to get more information). -

+ {% if dispatcherData['called_listeners'] is empty %} +
+

No events have been recorded. Check that debugging is enabled in the kernel.

{% else %} - {{ helper.render_table(collector.notcalledlisteners) }} - {% endif %} -
-
+
+
+

Called Listeners {{ dispatcherData['called_listeners']|length }}

-
-

Orphaned Events {{ collector.orphanedEvents|length }}

-
- {% if collector.orphanedEvents is empty %} -
-

- There are no orphaned events. -

-

- All dispatched events were handled or an error occurred - when trying to collect orphaned events (in which case check the - logs to get more information). -

+
+ {{ _self.render_table(dispatcherData['called_listeners']) }} +
+
+ +
+

Not Called Listeners {{ dispatcherData['not_called_listeners']|length }}

+
+ {% if dispatcherData['not_called_listeners'] is empty %} +
+

+ There are no uncalled listeners. +

+

+ All listeners were called for this request or an error occurred + when trying to collect uncalled listeners (in which case check the + logs to get more information). +

+
+ {% else %} + {{ _self.render_table(dispatcherData['not_called_listeners']) }} + {% endif %} +
+
+ +
+

Orphaned Events {{ dispatcherData['orphaned_events']|length }}

+
+ {% if dispatcherData['orphaned_events'] is empty %} +
+

+ There are no orphaned events. +

+

+ All dispatched events were handled or an error occurred + when trying to collect orphaned events (in which case check the + logs to get more information). +

+
+ {% else %} + + + + + + + + {% for event in dispatcherData['orphaned_events'] %} + + + + {% endfor %} + +
Event
{{ event }}
+ {% endif %} +
+
- {% else %} - - - - - - - - {% for event in collector.orphanedEvents %} - - - - {% endfor %} - -
Event
{{ event }}
{% endif %}
-
- {% endif %} + {% endfor %} +
{% endblock %} {% macro render_table(listeners) %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig index 81973186f563b..fb5b8b7dadaff 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -1,7 +1,5 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% from _self import form_tree_entry, form_tree_details %} - {% block toolbar %} {% if collector.data.nb_errors > 0 or collector.data.forms|length %} {% set status_color = collector.data.nb_errors ? 'red' %} @@ -43,6 +41,27 @@ {{ parent() }} +{% endblock %} + + {% block toolbar %} {% if collector.requestCount %} {% set icon %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index 937b4a6449e80..78de5763f7610 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -1,6 +1,239 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} +{% block head %} + {{ parent() }} + + +{% endblock %} {% block toolbar %} {% if collector.counterrors or collector.countdeprecations or collector.countwarnings %} @@ -57,28 +290,25 @@ {% set filters = collector.filters %}
-
    -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • -
+
+ {% set initially_active_tab = has_error_logs ? 'error' : has_deprecation_logs ? 'deprecation' : 'all' %} + + + + + + + + +
@@ -126,7 +356,7 @@ - + @@ -145,7 +375,7 @@ %} {% endfor %} @@ -179,11 +409,7 @@ {% endif %} - {% set compilerLogTotal = 0 %} - {% for logs in collector.compilerLogs %} - {% set compilerLogTotal = compilerLogTotal + logs|length %} - {% endfor %} - + {% set compilerLogTotal = collector.compilerLogs|reduce((total, logs) => total + logs|length, 0) %}

Container Compilation Logs ({{ compilerLogTotal }})

diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig index a42b8b33dd32e..11b5ae07f53b1 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig +++ b/src/Symfony/Bundle/W B41A ebProfilerBundle/Resources/views/Collector/mailer.html.twig @@ -1,5 +1,163 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% set events = collector.events %} @@ -30,7 +188,7 @@ {{ source('@WebProfiler/Icon/mailer.svg') }} - E-mails + Emails {% if events.messages|length > 0 %} {{ events.messages|length }} @@ -151,7 +309,7 @@ {% if message.attachments %}
{% set num_of_attachments = message.attachments|length %} - {% set total_attachments_size_in_bytes = message.attachments|reduce((total_size, attachment) => total_size + attachment.body|length) %} + {% set total_attachments_size_in_bytes = message.attachments|reduce((total_size, attachment) => total_size + attachment.body|length, 0) %}

{{ source('@WebProfiler/Icon/attachment.svg') }} Attachments ({{ num_of_attachments }} file{{ num_of_attachments > 1 ? 's' }} / {{ _self.render_file_size_humanized(total_attachments_size_in_bytes) }}) @@ -177,54 +335,59 @@

{% endif %} - {% if message.htmlBody or message.textBody %} -
-
- {% if message.htmlBody %} - {% set htmlBody = message.htmlBody() %} -
-

HTML preview

-
- +
+ {% set textBody = message.textBody %} + {% set htmlBody = message.htmlBody %} +
+
+

Text content

+
+ {% if textBody %} +
+                                                {%- if message.textCharset() %}
+                                                    {{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
+                                                {%- else %}
+                                                    {{- textBody }}
+                                                {%- endif -%}
+                                            
+ {% else %} +
+

The text body is empty.

-
+ {% endif %} +
+
-
-

HTML content

-
-
-                                                    {%- if message.htmlCharset() %}
-                                                        {{- htmlBody|convert_encoding('UTF-8', message.htmlCharset()) }}
-                                                    {%- else %}
-                                                        {{- htmlBody }}
-                                                    {%- endif -%}
-                                                
-
+ {% if htmlBody %} +
+

HTML preview

+
+

+                                            
- {% endif %} - - {% if message.textBody %} - {% set textBody = message.textBody() %} -
-

Text content

-
-
-                                                    {%- if message.textCharset() %}
-                                                        {{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
-                                                    {%- else %}
-                                                        {{- textBody }}
-                                                    {%- endif -%}
-                                                
+
+ {% endif %} + +
+

HTML content

+
+ {% if htmlBody %} +
+                                                {%- if message.htmlCharset() %}
+                                                    {{- htmlBody|convert_encoding('UTF-8', message.htmlCharset()) }}
+                                                {%- else %}
+                                                    {{- htmlBody }}
+                                                {%- endif -%}
+                                            
+ {% else %} +
+

The HTML body is empty.

-
- {% endif %} + {% endif %} +
- {% endif %} +
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig index 1dc5accf1c3a4..d803d6f2f80d0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig @@ -1,6 +1,42 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} +{% block head %} + {{ parent() }} + + +{% endblock %} {% block toolbar %} {% if collector.messages|length > 0 %} @@ -41,28 +77,7 @@ {% endblock %} -{% block head %} - {{ parent() }} - -{% endblock %} - {% block panel %} - {% import _self as helper %} -

Messages

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

Ordered list of dispatched messages across all your buses

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

Ordered list of dispatched messages across all your buses

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

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

- {{ helper.render_bus_messages(messages) }} + {{ _self.render_bus_messages(messages) }}
{% endfor %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig index 1f51c3284d648..7d108394f37da 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig @@ -29,7 +29,7 @@ {% block head %} {{ parent() }} - +{% endblock %} + {% block toolbar %} {% set request_handler %} {{ _self.set_handler(collector.controller) }} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig index 455c49839d296..94540fe1a59c9 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig @@ -1,6 +1,32 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} +{% block head %} + {{ parent() }} + + +{% endblock %} {% block toolbar %} {% if collector.handledCount > 0 %} @@ -89,14 +115,14 @@
- {{ helper.render_serialize_tab(collector.data, true) }} - {{ helper.render_serialize_tab(collector.data, false) }} + {{ _self.render_serialize_tab(collector.data, true) }} + {{ _self.render_serialize_tab(collector.data, false) }} - {{ helper.render_normalize_tab(collector.data, true) }} - {{ helper.render_normalize_tab(collector.data, false) }} + {{ _self.render_normalize_tab(collector.data, true) }} + {{ _self.render_normalize_tab(collector.data, false) }} - {{ helper.render_encode_tab(collector.data, true) }} - {{ helper.render_encode_tab(collector.data, false) }} + {{ _self.render_encode_tab(collector.data, true) }} + {{ _self.render_encode_tab(collector.data, false) }}
{% endif %}
@@ -128,12 +154,12 @@
{% for item in data %} - - - - - - + + + + + + {% endfor %} @@ -198,11 +224,11 @@ {% for item in data %} - - - - - + + + + + {% endfor %} @@ -237,11 +263,11 @@ {% for item in data %} - - - - - + + + + + {% endfor %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig index 9db62e5b439a7..2fb4e0a848f35 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig @@ -1,6 +1,42 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} +{% block head %} + {{ parent() }} + + +{% endblock %} {% block toolbar %} {% set has_time_events = collector.events|length > 0 %} @@ -70,14 +106,9 @@ Sub-Request{{ profile.children|length > 1 ? 's' }} - {% if has_time_events %} - {% set subrequests_time = 0 %} - {% for child in profile.children %} - {% set subrequests_time = subrequests_time + child.getcollector('time').events.__section__.duration %} - {% endfor %} - {% else %} - {% set subrequests_time = 'n/a' %} - {% endif %} + {% set subrequests_time = has_time_events + ? profile.children|reduce((total, child) => total + child.getcollector('time').events.__section__.duration, 0) + : 'n/a' %}
{{ subrequests_time }} ms @@ -124,7 +155,7 @@ {% endif %} - {{ helper.display_timeline(token, collector.events, collector.events.__section__.origin) }} + {{ _self.display_timeline(token, collector.events, collector.events.__section__.origin) }} {% if profile.children|length %}

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

@@ -138,7 +169,7 @@ {{ events.__section__.duration }} ms - {{ helper.display_timeline(child.token, events, collector.events.__section__.origin) }} + {{ _self.display_timeline(child.token, events, collector.events.__section__.origin) }} {% endfor %} {% endif %} @@ -149,7 +180,7 @@ - diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig index 13503feeb4c05..c8190f5bfec66 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig @@ -1,7 +1,5 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} -{% import _self as helper %} - {% block toolbar %} {% if collector.messages|length %} {% set icon %} @@ -105,7 +103,7 @@
{% else %} {% block defined_messages %} - {{ helper.render_table(messages_defined) }} + {{ _self.render_table(messages_defined) }} {% endblock %} {% endif %} @@ -126,7 +124,7 @@ {% else %} {% block fallback_messages %} - {{ helper.render_table(messages_fallback, true) }} + {{ _self.render_table(messages_fallback, true) }} {% endblock %} {% endif %} @@ -148,7 +146,7 @@ {% else %} {% block missing_messages %} - {{ helper.render_table(messages_missing) }} + {{ _self.render_table(messages_missing) }} {% endblock %} {% endif %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig index f4626403a88d8..2e375bb989701 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig @@ -1,5 +1,42 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% set time = collector.templatecount ? '%0.0f'|format(collector.time) : 'n/a' %} {% set icon %} @@ -9,6 +46,21 @@ {% endset %} {% set text %} + {% set template = collector.templates|keys|first %} + {% set file = collector.templatePaths[template]|default(false) %} + {% set link = file ? file|file_link(1) : false %} +
+ Entry View + + {% if link %} + + {{ template }} + + {% else %} + {{ template }} + {% endif %} + +
Render Time {{ time }} ms diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig index a473ac2372bac..daad8404e4cb3 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig @@ -1,5 +1,34 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block head %} + {{ parent() }} + + +{% endblock %} + {% block toolbar %} {% if collector.violationsCount > 0 or collector.calls|length %} {% set status_color = collector.violationsCount ? 'red' %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg index f357a626a0281..be85557836b5e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg @@ -1,4 +1,5 @@ - + + Cache diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg index e28a1c6aa8c15..8f76cf1791bf4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg @@ -1,4 +1,5 @@ - + + Config diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg index a7a79696dfc63..571aed9c56385 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg @@ -1,4 +1,5 @@ - + + Exception diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg index 5a5cee29486e0..55dd62b6fb5a0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg @@ -1,4 +1,5 @@ - + + Cache diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg index ee72a2c5f329d..39a3adffd53b6 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg @@ -1,4 +1,5 @@ - + + Logger diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg index e489e1122a819..8e05e6ef6866e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg @@ -1,4 +1,5 @@ - + + Memory diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg index 838deca87a298..4805aed725ba8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg @@ -1,4 +1,5 @@ - + + Menu diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg index ebf9b6479b32d..a23a9710ffd0a 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg @@ -1,4 +1,5 @@ - + + Redirect diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg index 64bdeac785005..3c8c65fbe0acb 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg @@ -1,4 +1,5 @@ - + + Time diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg index 1211c61066b18..027cb2f11f88e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg @@ -1,4 +1,5 @@ - + + Twig diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig index 1a74431d898b5..c037c07ec94bc 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig @@ -5,7 +5,11 @@ {% block title %}Symfony Profiler{% endblock %} - + + {% set request_collector = profile is defined ? profile.collectors.request|default(null) : null %} + {% set status_code = request_collector is not null ? request_collector.statuscode|default(0) : 0 %} + {% set favicon_color = status_code > 399 ? 'b41939' : status_code > 299 ? 'af8503' : '000000' %} + {% block head %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index ff7c3d93642dd..4b4a114045483 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -663,23 +663,31 @@ if (typeof Sfjs === 'undefined' || typeof Sfjs.loadToolbar === 'undefined') { }, createTabs: function() { + /* the accessibility options of this component have been defined according to: */ + /* www.w3.org/WAI/ARIA/apg/example-index/tabs/tabs-manual.html */ var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])'); /* create the tab navigation for each group of tabs */ for (var i = 0; i < tabGroups.length; i++) { var tabs = tabGroups[i].querySelectorAll(':scope > .tab'); - var tabNavigation = document.createElement('ul'); + var tabNavigation = document.createElement('div'); tabNavigation.className = 'tab-navigation'; + tabNavigation.setAttribute('role', 'tablist'); var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */ for (var j = 0; j < tabs.length; j++) { var tabId = 'tab-' + i + '-' + j; var tabTitle = tabs[j].querySelector('.tab-title').innerHTML; - var tabNavigationItem = document.createElement('li'); + var tabNavigationItem = document.createElement('button'); + tabNavigationItem.classList.add('tab-control'); tabNavigationItem.setAttribute('data-tab-id', tabId); + tabNavigationItem.setAttribute('role', 'tab'); + tabNavigationItem.setAttribute('aria-controls', tabId); if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; } - if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); } + if (hasClass(tabs[j], 'disabled')) { + addClass(tabNavigationItem, 'disabled'); + } tabNavigationItem.innerHTML = tabTitle; tabNavigation.appendChild(tabNavigationItem); @@ -693,24 +701,31 @@ if (typeof Sfjs === 'undefined' || typeof Sfjs.loadToolbar === 'undefined') { /* display the active tab and add the 'click' event listeners */ for (i = 0; i < tabGroups.length; i++) { - tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation li'); + tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation .tab-control'); for (j = 0; j < tabNavigation.length; j++) { tabId = tabNavigation[j].getAttribute('data-tab-id'); - document.getElementById(tabId).querySelector('.tab-title').className = 'hidden'; + const tabPanel = document.getElementById(tabId); + tabPanel.setAttribute('role', 'tabpanel'); + tabPanel.setAttribute('aria-labelledby', tabId); + tabPanel.querySelector('.tab-title').className = 'hidden'; if (hasClass(tabNavigation[j], 'active')) { - document.getElementById(tabId).className = 'block'; + tabPanel.className = 'block'; + tabNavigation[j].setAttribute('aria-selected', 'true'); + tabNavigation[j].removeAttribute('tabindex'); } else { - document.getElementById(tabId).className = 'hidden'; + tabPanel.className = 'hidden'; + tabNavigation[j].removeAttribute('aria-selected'); + tabNavigation[j].setAttribute('tabindex', '-1'); } tabNavigation[j].addEventListener('click', function(e) { var activeTab = e.target || e.srcElement; /* needed because when the tab contains HTML contents, user can click */ - /* on any of those elements instead of their parent '
  • ' element */ - while (activeTab.tagName.toLowerCase() !== 'li') { + /* on any of those elements instead of their parent '
  • diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/settings.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/settings.html.twig index 71080c737782a..9f96c5b881eb6 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/settings.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/settings.html.twig @@ -41,7 +41,9 @@ } .modal-wrap { - -webkit-transition: all 0.3s ease-in-out; + -webkit-transition-duration: 0.3s; + -webkit-transition-property: opacity, visibility; + -webkit-transition-timing-function: ease-in-out; align-items: center; background: rgba(0, 0, 0, 0.70); display: flex; @@ -49,11 +51,13 @@ height: 100%; justify-content: center; left: 0; - opacity: 1; + opacity: 0; overflow: auto; position: fixed; top: 0; - transition: all 0.3s ease-in-out; + transition-duration: 0.3s; + transition-property: opacity, visibility; + transition-timing-function: ease-in-out; visibility: hidden; width: 100%; z-index: 100000; @@ -195,7 +199,7 @@ - < F438 div class="exception-message-wrapper">

    formatFileFromText(nl2br($exceptionMessage)); ?>

    diff --git a/src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php index 38752bc1a892f..fd834c144e881 100644 --- a/src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php +++ b/src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php @@ -25,6 +25,14 @@

    escape($exception['message']); ?>

    + +
    + Show exception properties +
    + dumpValue($exception['data']) ?> +
    +
    +
    diff --git a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php index edb58512f32e0..87db0bf4b5a80 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php @@ -120,7 +120,7 @@ public function testClassAlias() */ public function testDeprecatedSuper(string $class, string $super, string $type) { - set_error_handler(function () { return false; }); + set_error_handler(fn () => false); $e = error_reporting(0); trigger_error('', E_USER_DEPRECATED); @@ -150,7 +150,7 @@ public static function provideDeprecatedSuper(): array public function testInterfaceExtendsDeprecatedInterface() { - set_error_handler(function () { return false; }); + set_error_handler(fn () => false); $e = error_reporting(0); trigger_error('', E_USER_NOTICE); @@ -172,7 +172,7 @@ class_exists('Test\\'.NonDeprecatedInterfaceClass::class, true); public function testDeprecatedSuperInSameNamespace() { - set_error_handler(function () { return false; }); + set_error_handler(fn () => false); $e = error_reporting(0); trigger_error('', E_USER_NOTICE); @@ -242,7 +242,7 @@ class_exists(Fixtures\ExtendedFinalMethod::class, true); public function testExtendedDeprecatedMethodDoesntTriggerAnyNotice() { - set_error_handler(function () { return false; }); + set_error_handler(fn () => false); $e = error_reporting(0); trigger_error('', E_USER_NOTICE); diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php index 3926b730d1510..857592adea033 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php @@ -408,8 +408,8 @@ public static function handleExceptionProvider(): array ['Uncaught Exception: foo', new \Exception('foo')], ['Uncaught Exception: foo', new class('foo') extends \RuntimeException { }], - ['Uncaught Exception: foo stdClass@anonymous bar', new \RuntimeException('foo '.\get_class(new class() extends \stdClass { - }).' bar')], + ['Uncaught Exception: foo stdClass@anonymous bar', new \RuntimeException('foo '.(new class() extends \stdClass { + })::class.' bar')], ['Uncaught Error: bar', new \Error('bar')], ['Uncaught ccc', new \ErrorException('ccc')], ]; diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php index 77a28ecde69d6..211ed3177568a 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php @@ -31,7 +31,7 @@ public function testSerializerContent() $exception = new \RuntimeException('Foo'); $errorRenderer = new SerializerErrorRenderer( new Serializer([new ProblemNormalizer()], [new JsonEncoder()]), - function () { return 'json'; } + fn () => 'json' ); $this->assertSame('{"type":"https:\/\/tools.ietf.org\/html\/rfc2616#section-10","title":"An error occurred","status":500,"detail":"Internal Server Error"}', $errorRenderer->render($exception)->getAsString()); diff --git a/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php index b1fadb44dc59b..a3ff2f6b02987 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php @@ -209,6 +209,7 @@ public function testToArray(\Throwable $exception, string $expectedClass) 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123, 'args' => [], ]], + 'data' => null, ], ], $flattened->toArray()); } @@ -249,13 +250,13 @@ public function testAnonymousClass() $this->assertSame('RuntimeException@anonymous', $flattened->getClass()); - $flattened->setClass(\get_class(new class('Oops') extends NotFoundHttpException { - })); + $flattened->setClass((new class('Oops') extends NotFoundHttpException { + })::class); $this->assertSame('Symfony\Component\HttpKernel\Exception\NotFoundHttpException@anonymous', $flattened->getClass()); - $flattened = FlattenException::createFromThrowable(new \Exception(sprintf('Class "%s" blah.', \get_class(new class() extends \RuntimeException { - })))); + $flattened = FlattenException::createFromThrowable(new \Exception(sprintf('Class "%s" blah.', (new class() extends \RuntimeException { + })::class))); $this->assertSame('Class "RuntimeException@anonymous" blah.', $flattened->getMessage()); } @@ -272,9 +273,7 @@ public function testToStringEmptyMessage() public function testToString() { - $test = function ($a, $b, $c, $d) { - return new \RuntimeException('This is a test message'); - }; + $test = fn ($a, $b, $c, $d) => new \RuntimeException('This is a test message'); $exception = $test('foo123', 1, null, 1.5); @@ -294,9 +293,4 @@ public function testToStringParent() $this->assertSame($exception->getTraceAsString(), $flattened->getTraceAsString()); $this->assertSame($exception->__toString(), $flattened->getAsString()); } - - private function createException($foo): \Exception - { - return new \Exception(); - } } diff --git a/src/Symfony/Component/ErrorHandler/composer.json b/src/Symfony/Component/ErrorHandler/composer.json index 5331dd88366c6..03eec618df8fb 100644 --- a/src/Symfony/Component/ErrorHandler/composer.json +++ b/src/Symfony/Component/ErrorHandler/composer.json @@ -23,7 +23,10 @@ "require-dev": { "symfony/http-kernel": "^5.4|^6.0", "symfony/serializer": "^5.4|^6.0", - "symfony/deprecation-contracts": "^2.1|^3" + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" }, "autoload": { "psr-4": { "Symfony\\Component\\ErrorHandler\\": "" }, diff --git a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php index d821378114cc2..f1b982315ce52 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php @@ -51,16 +51,25 @@ public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $sto $this->requestStack = $requestStack; } + /** + * @return void + */ public function addListener(string $eventName, callable|array $listener, int $priority = 0) { $this->dispatcher->addListener($eventName, $listener, $priority); } + /** + * @return void + */ public function addSubscriber(EventSubscriberInterface $subscriber) { $this->dispatcher->addSubscriber($subscriber); } + /** + * @return void + */ public function removeListener(string $eventName, callable|array $listener) { if (isset($this->wrappedListeners[$eventName])) { @@ -73,12 +82,15 @@ public function removeListener(string $eventName, callable|array $listener) } } - return $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->removeListener($eventName, $listener); } + /** + * @return void + */ public function removeSubscriber(EventSubscriberInterface $subscriber) { - return $this->dispatcher->removeSubscriber($subscriber); + $this->dispatcher->removeSubscriber($subscriber); } public function getListeners(string $eventName = null): array @@ -214,6 +226,9 @@ public function getOrphanedEvents(Request $request = null): array return array_merge(...array_values($this->orphanedEvents)); } + /** + * @return void + */ public function reset() { $this->callStack = null; @@ -234,6 +249,8 @@ public function __call(string $method, array $arguments): mixed /** * Called before dispatching the event. + * + * @return void */ protected function beforeDispatch(string $eventName, object $event) { @@ -241,6 +258,8 @@ protected function beforeDispatch(string $eventName, object $event) /** * Called after dispatching the event. + * + * @return void */ protected function afterDispatch(string $eventName, object $event) { diff --git a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php index 5871578c82587..6e0de1dff811c 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php +++ b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php @@ -136,7 +136,7 @@ private function parseListener(array $listener): array } if (\is_object($listener[0])) { - return [get_debug_type($listener[0]), \get_class($listener[0])]; + return [get_debug_type($listener[0]), $listener[0]::class]; } return [$listener[0], $listener[0]]; diff --git a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php index 57b7bf9bf8637..c86f438d41ee1 100644 --- a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php +++ b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php @@ -48,6 +48,9 @@ public function setNoPreloadEvents(array $noPreloadEvents): static return $this; } + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('event_dispatcher') && !$container->hasAlias('event_dispatcher')) { @@ -83,7 +86,7 @@ public function process(ContainerBuilder $container) $event['method'] = 'on'.preg_replace_callback([ '/(?<=\b|_)[a-z]/i', '/[^a-z0-9]/i', - ], function ($matches) { return strtoupper($matches[0]); }, $event['event']); + ], fn ($matches) => strtoupper($matches[0]), $event['event']); $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) { @@ -191,7 +194,7 @@ class ExtractingEventDispatcher extends EventDispatcher implements EventSubscrib public static array $aliases = []; public static string $subscriber; - public function addListener(string $eventName, callable|array $listener, int $priority = 0) + public function addListener(string $eventName, callable|array $listener, int $priority = 0): void { $this->listeners[] = [$eventName, $listener[1], $priority]; } diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcher.php b/src/Symfony/Component/EventDispatcher/EventDispatcher.php index 41f3f7ae7c848..327803af671c7 100644 --- a/src/Symfony/Component/EventDispatcher/EventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/EventDispatcher.php @@ -123,12 +123,18 @@ public function hasListeners(string $eventName = null): bool return false; } + /** + * @return void + */ public function addListener(string $eventName, callable|array $listener, int $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; unset($this->sorted[$eventName], $this->optimized[$eventName]); } + /** + * @return void + */ public function removeListener(string $eventName, callable|array $listener) { if (empty($this->listeners[$eventName])) { @@ -157,6 +163,9 @@ public function removeListener(string $eventName, callable|array $listener) } } + /** + * @return void + */ public function addSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { @@ -172,6 +181,9 @@ public function addSubscriber(EventSubscriberInterface $subscriber) } } + /** + * @return void + */ public function removeSubscriber(EventSubscriberInterface $subscriber) { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { @@ -194,6 +206,8 @@ public function removeSubscriber(EventSubscriberInterface $subscriber) * @param callable[] $listeners The event listeners * @param string $eventName The name of the event to dispatch * @param object $event The event object to pass to the event handlers/listeners + * + * @return void */ protected function callListeners(iterable $listeners, string $eventName, object $event) { @@ -210,7 +224,7 @@ protected function callListeners(iterable $listeners, string $eventName, object /** * Sorts the internal list of listeners for the given event by priority. */ - private function sortListeners(string $eventName) + private function sortListeners(string $eventName): void { krsort($this->listeners[$eventName]); $this->sorted[$eventName] = []; diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php b/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php index 97a3017a44c88..3cd94c93886f8 100644 --- a/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php +++ b/src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php @@ -27,6 +27,8 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface * * @param int $priority The higher this value, the earlier an event * listener will be triggered in the chain (defaults to 0) + * + * @return void */ public function addListener(string $eventName, callable $listener, int $priority = 0); @@ -35,14 +37,21 @@ public function addListener(string $eventName, callable $listener, int $priority * * The subscriber is asked for all the events it is * interested in and added as a listener for these events. + * + * @return void */ public function addSubscriber(EventSubscriberInterface $subscriber); /** * Removes an event listener from the specified events. + * + * @return void */ public function removeListener(string $eventName, callable $listener); + /** + * @return void + */ public function removeSubscriber(EventSubscriberInterface $subscriber); /** diff --git a/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php index 182ba080d54f6..d385d3f8339ec 100644 --- a/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php @@ -30,21 +30,33 @@ public function dispatch(object $event, string $eventName = null): object return $this->dispatcher->dispatch($event, $eventName); } + /** + * @return never + */ public function addListener(string $eventName, callable|array $listener, int $priority = 0) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } + /** + * @return never + */ public function addSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } + /** + * @return never + */ public function removeListener(string $eventName, callable|array $listener) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } + /** + * @return never + */ public function removeSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); diff --git a/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php b/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php index a13e6f43f06f6..59f6fcbebfb37 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php @@ -347,7 +347,7 @@ public function testDispatchLazyListener() public function testRemoveFindsLazyListeners() { $test = new TestWithDispatcher(); - $factory = function () use ($test) { return $test; }; + $factory = fn () => $test; $this->dispatcher->addListener('foo', [$factory, 'foo']); $this->assertTrue($this->dispatcher->hasListeners('foo')); @@ -363,7 +363,7 @@ public function testRemoveFindsLazyListeners() public function testPriorityFindsLazyListeners() { $test = new TestWithDispatcher(); - $factory = function () use ($test) { return $test; }; + $factory = fn () => $test; $this->dispatcher->addListener('foo', [$factory, 'foo'], 3); $this->assertSame(3, $this->dispatcher->getListenerPriority('foo', [$test, 'foo'])); @@ -376,7 +376,7 @@ public function testPriorityFindsLazyListeners() public function testGetLazyListeners() { $test = new TestWithDispatcher(); - $factory = function () use ($test) { return $test; }; + $factory = fn () => $test; $this->dispatcher->addListener('foo', [$factory, 'foo'], 3); $this->assertSame([[$test, 'foo']], $this->dispatcher->getListeners('foo')); diff --git a/src/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php b/src/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php index 403bd7f4e913c..35bab1a9325da 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php @@ -75,7 +75,7 @@ public function testHasListenersDelegates() public function testAddListenerDisallowed() { $this->expectException(\BadMethodCallException::class); - $this->dispatcher->addListener('event', function () { return 'foo'; }); + $this->dispatcher->addListener('event', fn () => 'foo'); } public function testAddSubscriberDisallowed() @@ -89,7 +89,7 @@ public function testAddSubscriberDisallowed() public function testRemoveListenerDisallowed() { $this->expectException(\BadMethodCallException::class); - $this->dispatcher->removeListener('event', function () { return 'foo'; }); + $this->dispatcher->removeListener('event', fn () => 'foo'); } public function testRemoveSubscriberDisallowed() diff --git a/src/Symfony/Component/EventDispatcher/composer.json b/src/Symfony/Component/EventDispatcher/composer.json index c05373f331c1a..287037822d04d 100644 --- a/src/Symfony/Component/EventDispatcher/composer.json +++ b/src/Symfony/Component/EventDispatcher/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^2|^3" + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "require-dev": { "symfony/dependency-injection": "^5.4|^6.0", @@ -25,21 +25,18 @@ "symfony/config": "^5.4|^6.0", "symfony/error-handler": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/stopwatch": "^5.4|^6.0", "psr/log": "^1|^2|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4" + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", "symfony/event-dispatcher-implementation": "2.0|3.0" }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md index 9210fc4cc33fb..b06620cd79acb 100644 --- a/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md +++ b/src/Symfony/Component/ExpressionLanguage/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +6.3 +--- + + * Add `enum` expression function + * Deprecate loose comparisons when using the "in" operator; normalize the array parameter + so it only has the expected types or implement loose matching in your own expression function + 6.2 --- diff --git a/src/Symfony/Component/ExpressionLanguage/Compiler.php b/src/Symfony/Component/ExpressionLanguage/Compiler.php index b52d41ee67f90..ab50d361e3dc9 100644 --- a/src/Symfony/Component/ExpressionLanguage/Compiler.php +++ b/src/Symfony/Component/ExpressionLanguage/Compiler.php @@ -28,6 +28,9 @@ public function __construct(array $functions) $this->functions = $functions; } + /** + * @return array + */ public function getFunction(string $name) { return $this->functions[$name]; @@ -63,6 +66,9 @@ public function compile(Node\Node $node): static return $this; } + /** + * @return string + */ public function subcompile(Node\Node $node) { $current = $this->source; diff --git a/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php b/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php index e1f8ebc0919f1..d0ddd10f7d83f 100644 --- a/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php +++ b/src/Symfony/Component/ExpressionLanguage/ExpressionFunction.php @@ -82,13 +82,9 @@ public static function fromPhp(string $phpFunctionName, string $expressionFuncti throw new \InvalidArgumentException(sprintf('An expression function name must be defined when PHP function "%s" is namespaced.', $phpFunctionName)); } - $compiler = function (...$args) use ($phpFunctionName) { - return sprintf('\%s(%s)', $phpFunctionName, implode(', ', $args)); - }; + $compiler = fn (...$args) => sprintf('\%s(%s)', $phpFunctionName, implode(', ', $args)); - $evaluator = function ($p, ...$args) use ($phpFunctionName) { - return $phpFunctionName(...$args); - }; + $evaluator = fn ($p, ...$args) => $phpFunctionName(...$args); return new self($expressionFunctionName ?: end($parts), $compiler, $evaluator); } diff --git a/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php b/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php index e076eb9bc56e0..9e107401a2d6a 100644 --- a/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php +++ b/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php @@ -110,6 +110,8 @@ public function lint(Expression|string $expression, ?array $names): void * @param callable $compiler A callable able to compile the function * @param callable $evaluator A callable able to evaluate the function * + * @return void + * * @throws \LogicException when registering a function after calling evaluate(), compile() or parse() * * @see ExpressionFunction @@ -123,11 +125,17 @@ public function register(string $name, callable $compiler, callable $evaluator) $this->functions[$name] = ['compiler' => $compiler, 'evaluator' => $evaluator]; } + /** + * @return void + */ public function addFunction(ExpressionFunction $function) { $this->register($function->getName(), $function->getCompiler(), $function->getEvaluator()); } + /** + * @return void + */ public function registerProvider(ExpressionFunctionProviderInterface $provider) { foreach ($provider->getFunctions() as $function) { @@ -135,9 +143,25 @@ public function registerProvider(ExpressionFunctionProviderInterface $provider) } } + /** + * @return void + */ protected function registerFunctions() { $this->addFunction(ExpressionFunction::fromPhp('constant')); + + $this->addFunction(new ExpressionFunction('enum', + static fn ($str): string => sprintf("(\constant(\$v = (%s))) instanceof \UnitEnum ? \constant(\$v) : throw new \TypeError(\sprintf('The string \"%%s\" is not the name of a valid enum case.', \$v))", $str), + static function ($arguments, $str): \UnitEnum { + $value = \constant($str); + + if (!$value instanceof \UnitEnum) { + throw new \TypeError(sprintf('The string "%s" is not the name of a valid enum case.', $str)); + } + + return $value; + } + )); } private function getLexer(): Lexer diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php index e9849a448a624..981cae21c61f6 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ArgumentsNode.php @@ -20,12 +20,12 @@ */ class ArgumentsNode extends ArrayNode { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $this->compileArguments($compiler, false); } - public function toArray() + public function toArray(): array { $array = []; diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php index e1a3169b0030c..e75de1af2aa69 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ArrayNode.php @@ -27,7 +27,7 @@ public function __construct() $this->index = -1; } - public function addElement(Node $value, Node $key = null) + public function addElement(Node $value, Node $key = null): void { $key ??= new ConstantNode(++$this->index); @@ -37,14 +37,14 @@ public function addElement(Node $value, Node $key = null) /** * Compiles the node to PHP. */ - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->raw('['); $this->compileArguments($compiler); $compiler->raw(']'); } - public function evaluate(array $functions, array $values) + public function evaluate(array $functions, array $values): array { $result = []; foreach ($this->getKeyValuePairs() as $pair) { @@ -54,7 +54,7 @@ public function evaluate(array $functions, array $values) return $result; } - public function toArray() + public function toArray(): array { $value = []; foreach ($this->getKeyValuePairs() as $pair) { @@ -84,7 +84,7 @@ public function toArray() return $array; } - protected function getKeyValuePairs() + protected function getKeyValuePairs(): array { $pairs = []; foreach (array_chunk($this->nodes, 2) as $pair) { @@ -94,7 +94,7 @@ protected function getKeyValuePairs() return $pairs; } - protected function compileArguments(Compiler $compiler, bool $withKeys = true) + protected function compileArguments(Compiler $compiler, bool $withKeys = true): void { $first = true; foreach ($this->getKeyValuePairs() as $pair) { diff --git a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php index 745fd26dc4cee..48331167bd275 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php @@ -30,8 +30,8 @@ class BinaryNode extends Node private const FUNCTIONS = [ '**' => 'pow', '..' => 'range', - 'in' => 'in_array', - 'not in' => '!in_array', + 'in' => '\\'.self::class.'::inArray', + 'not in' => '!\\'.self::class.'::inArray', 'contains' => 'str_contains', 'starts with' => 'str_starts_with', 'ends with' => 'str_ends_with', @@ -45,7 +45,7 @@ public function __construct(string $operator, Node $left, Node $right) ); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $operator = $this->attributes['operator']; @@ -92,7 +92,7 @@ public function compile(Compiler $compiler) ; } - public function evaluate(array $functions, array $values) + public function evaluate(array $functions, array $values): mixed { $operator = $this->attributes['operator']; $left = $this->nodes['left']->evaluate($functions, $values); @@ -101,7 +101,7 @@ public function evaluate(array $functions, array $values) $right = $this->nodes['right']->evaluate($functions, $values); if ('not in' === $operator) { - return !\in_array($left, $right); + return !self::inArray($left, $right); } $f = self::FUNCTIONS[$operator]; @@ -143,9 +143,9 @@ public function evaluate(array $functions, array $values) case '<=': return $left <= $right; case 'not in': - return !\in_array($left, $right); + return !self::inArray($left, $right); case 'in': - return \in_array($left, $right); + return self::inArray($left, $right); case '+': return $left + $right; case '-': @@ -171,11 +171,27 @@ public function evaluate(array $functions, array $values) } } - public function toArray() + public function toArray(): array { return ['(', $this->nodes['left'], ' '.$this->attributes['operator'].' ', $this->nodes['right'], ')']; } + /** + * @internal to be replaced by an inline strict call to in_array() in version 7.0 + */ + public static function inArray($value, array $array): bool + { + if (false === $key = array_search($value, $array)) { + return false; + } + + if (!\in_array($value, $array, true)) { + trigger_deprecation('symfony/expression-language', '6.3', 'The "in" operator will use strict comparisons in Symfony 7.0. Loose match found with key "%s" for value %s. Normalize the array parameter so it only has the expected types or implement loose matching in your own expression function.', $key, json_encode($value)); + } + + return true; + } + private function evaluateMatches(string $regexp, ?string $str): int { set_error_handler(function ($t, $m) use ($regexp) { diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php index ba78a2848eeab..7d02544275098 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ConditionalNode.php @@ -27,7 +27,7 @@ public function __construct(Node $expr1, Node $expr2, Node $expr3) ); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('((') @@ -40,7 +40,7 @@ public function compile(Compiler $compiler) ; } - public function evaluate(array $functions, array $values) + public function evaluate(array $functions, array $values): mixed { if ($this->nodes['expr1']->evaluate($functions, $values)) { return $this->nodes['expr2']->evaluate($functions, $values); @@ -49,7 +49,7 @@ public function evaluate(array $functions, array $values) return $this->nodes['expr3']->evaluate($functions, $values); } - public function toArray() + public function toArray(): array { return ['(', $this->nodes['expr1'], ' ? ', $this->nodes['expr2'], ' : ', $this->nodes['expr3'], ')']; } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php b/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php index 869e350dc9d4c..37beee86f8863 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/ConstantNode.php @@ -33,17 +33,17 @@ public function __construct(mixed $value, bool $isIdentifier = false, bool $isNu ); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->repr($this->attributes['value']); } - public function evaluate(array $functions, array $values) + public function evaluate(array $functions, array $values): mixed { return $this->attributes['value']; } - public function toArray() + public function toArray(): array { $array = []; $value = $this->attributes['value']; diff --git a/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php b/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php index 37b5982091ad3..33323f388f045 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/FunctionNode.php @@ -28,7 +28,7 @@ public function __construct(string $name, Node $arguments) ); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $arguments = []; foreach ($this->nodes['arguments']->nodes as $node) { @@ -40,7 +40,7 @@ public function compile(Compiler $compiler) $compiler->raw($function['compiler'](...$arguments)); } - public function evaluate(array $functions, array $values) + public function evaluate(array $functions, array $values): mixed { $arguments = [$values]; foreach ($this->nodes['arguments']->nodes as $node) { @@ -50,6 +50,9 @@ public function evaluate(array $functions, array $values) return $functions[$this->attributes['name']]['evaluator'](...$arguments); } + /** + * @return array + */ public function toArray() { $array = []; diff --git a/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php b/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php index c348a3da569bc..984247e77b69a 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/GetAttrNode.php @@ -24,6 +24,9 @@ class GetAttrNode extends Node public const METHOD_CALL = 2; public const ARRAY_CALL = 3; + /** + * @param self::* $type + */ public function __construct(Node $node, Node $attribute, ArrayNode $arguments, int $type) { parent::__construct( @@ -32,7 +35,7 @@ public function __construct(Node $node, Node $attribute, ArrayNode $arguments, i ); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $nullSafe = $this->nodes['attribute'] instanceof ConstantNode && $this->nodes['attribute']->isNullSafe; switch ($this->attributes['type']) { @@ -65,7 +68,7 @@ public function compile(Compiler $compiler) } } - public function evaluate(array $functions, array $values) + public function evaluate(array $functions, array $values): mixed { switch ($this->attributes['type']) { case self::PROPERTY_CALL: @@ -136,7 +139,7 @@ private function isShortCircuited(): bool return $this->attributes['is_short_circuited'] || ($this->nodes['node'] instanceof self && $this->nodes['node']->isShortCircuited()); } - public function toArray() + public function toArray(): array { switch ($this->attributes['type']) { case self::PROPERTY_CALL: diff --git a/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php b/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php index e017e967a1d08..b9d0e1a11635f 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/NameNode.php @@ -28,17 +28,17 @@ public function __construct(string $name) ); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->raw('$'.$this->attributes['name']); } - public function evaluate(array $functions, array $values) + public function evaluate(array $functions, array $values): mixed { return $values[$this->attributes['name']]; } - public function toArray() + public function toArray(): array { return [$this->attributes['name']]; } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/Node.php b/src/Symfony/Component/ExpressionLanguage/Node/Node.php index 9b725174c6ea2..91fcc363edd50 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/Node.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/Node.php @@ -57,6 +57,9 @@ public function __toString(): string return implode("\n", $repr); } + /** + * @return void + */ public function compile(Compiler $compiler) { foreach ($this->nodes as $node) { @@ -64,6 +67,9 @@ public function compile(Compiler $compiler) } } + /** + * @return mixed + */ public function evaluate(array $functions, array $values) { $results = []; @@ -74,11 +80,19 @@ public function evaluate(array $functions, array $values) return $results; } + /** + * @return array + * + * @throws \BadMethodCallException when this node cannot be transformed to an array + */ public function toArray() { throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', static::class)); } + /** + * @return string + */ public function dump() { $dump = ''; @@ -90,11 +104,17 @@ public function dump() return $dump; } + /** + * @return string + */ protected function dumpString(string $value) { return sprintf('"%s"', addcslashes($value, "\0\t\"\\")); } + /** + * @return bool + */ protected function isHash(array $value) { $expectedKey = 0; diff --git a/src/Symfony/Component/ExpressionLanguage/Node/NullCoalesceNode.php b/src/Symfony/Component/ExpressionLanguage/Node/NullCoalesceNode.php index 3e59ef867611c..1cc5eb058e0cc 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/NullCoalesceNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/NullCoalesceNode.php @@ -25,7 +25,7 @@ public function __construct(Node $expr1, Node $expr2) parent::__construct(['expr1' => $expr1, 'expr2' => $expr2]); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('((') @@ -36,7 +36,7 @@ public function compile(Compiler $compiler) ; } - public function evaluate(array $functions, array $values) + public function evaluate(array $functions, array $values): mixed { if ($this->nodes['expr1'] instanceof GetAttrNode) { $this->nodes['expr1']->attributes['is_null_coalesce'] = true; @@ -45,7 +45,7 @@ public function evaluate(array $functions, array $values) return $this->nodes['expr1']->evaluate($functions, $values) ?? $this->nodes['expr2']->evaluate($functions, $values); } - public function toArray() + public function toArray(): array { return ['(', $this->nodes['expr1'], ') ?? (', $this->nodes['expr2'], ')']; } diff --git a/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php index 9e30d848eb4fc..55e2121fcf798 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/UnaryNode.php @@ -35,7 +35,7 @@ public function __construct(string $operator, Node $node) ); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('(') @@ -45,7 +45,7 @@ public function compile(Compiler $compiler) ; } - public function evaluate(array $functions, array $values) + public function evaluate(array $functions, array $values): mixed { $value = $this->nodes['node']->evaluate($functions, $values); diff --git a/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php b/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php index ca684ccd5f10a..239624ec2ccc4 100644 --- a/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php +++ b/src/Symfony/Component/ExpressionLanguage/ParsedExpression.php @@ -29,6 +29,9 @@ public function __construct(string $expression, Node $nodes) $this->nodes = $nodes; } + /** + * @return Node + */ public function getNodes() { return $this->nodes; diff --git a/src/Symfony/Component/ExpressionLanguage/Parser.php b/src/Symfony/Component/ExpressionLanguage/Parser.php index 0580b55d46e5b..a163a7a82f7d4 100644 --- a/src/Symfony/Component/ExpressionLanguage/Parser.php +++ b/src/Symfony/Component/ExpressionLanguage/Parser.php @@ -130,6 +130,9 @@ private function doParse(TokenStream $stream, ?array $names = []): Node\Node return $node; } + /** + * @return Node\Node + */ public function parseExpression(int $precedence = 0) { $expr = $this->getPrimary(); @@ -151,6 +154,9 @@ public function parseExpression(int $precedence = 0) return $expr; } + /** + * @return Node\Node + */ protected function getPrimary() { $token = $this->stream->current; @@ -174,6 +180,9 @@ protected function getPrimary() return $this->parsePrimaryExpression(); } + /** + * @return Node\Node + */ protected function parseConditionalExpression(Node\Node $expr) { while ($this->stream->current->test(Token::PUNCTUATION_TYPE, '??')) { @@ -205,6 +214,9 @@ protected function parseConditionalExpression(Node\Node $expr) return $expr; } + /** + * @return Node\Node + */ public function parsePrimaryExpression() { $token = $this->stream->current; @@ -270,6 +282,9 @@ public function parsePrimaryExpression() return $this->parsePostfixExpression($node); } + /** + * @return Node\ArrayNode + */ public function parseArrayExpression() { $this->stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); @@ -294,6 +309,9 @@ public function parseArrayExpression() return $node; } + /** + * @return Node\ArrayNode + */ public function parseHashExpression() { $this->stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); @@ -338,6 +356,9 @@ public function parseHashExpression() return $node; } + /** + * @return Node\GetAttrNode|Node\Node + */ public function parsePostfixExpression(Node\Node $node) { $token = $this->stream->current; @@ -350,7 +371,6 @@ public function parsePostfixExpression(Node\Node $node) if ( Token::NAME_TYPE !== $token->type - && // Operators like "not" and "matches" are valid method or property names, // // In other words, besides NAME_TYPE, OPERATOR_TYPE could also be parsed as a property or method. @@ -362,7 +382,7 @@ public function parsePostfixExpression(Node\Node $node) // Other types, such as STRING_TYPE and NUMBER_TYPE, can't be parsed as property nor method names. // // As a result, if $token is NOT an operator OR $token->value is NOT a valid property or method name, an exception shall be thrown. - (Token::OPERATOR_TYPE !== $token->type || !preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $token->value)) + && (Token::OPERATOR_TYPE !== $token->type || !preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $token->value)) ) { throw new SyntaxError('Expected name.', $token->cursor, $this->stream->getExpression()); } @@ -398,6 +418,8 @@ public function parsePostfixExpression(Node\Node $node) /** * Parses arguments. + * + * @return Node\Node */ public function parseArguments() { diff --git a/src/Symfony/Component/ExpressionLanguage/SerializedParsedExpression.php b/src/Symfony/Component/ExpressionLanguage/SerializedParsedExpression.php index 56207b09854ca..5691907c868c6 100644 --- a/src/Symfony/Component/ExpressionLanguage/SerializedParsedExpression.php +++ b/src/Symfony/Component/ExpressionLanguage/SerializedParsedExpression.php @@ -11,6 +11,8 @@ namespace Symfony\Component\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\Node\Node; + /** * Represents an already parsed expression. * @@ -30,6 +32,9 @@ public function __construct(string $expression, string $nodes) $this->nodes = $nodes; } + /** + * @return Node + */ public function getNodes() { return unserialize($this->nodes); diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php index 0f8f96e396500..bef2395e859c6 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php @@ -18,6 +18,8 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\ExpressionLanguage\ParsedExpression; use Symfony\Component\ExpressionLanguage\SyntaxError; +use Symfony\Component\ExpressionLanguage\Tests\Fixtures\FooBackedEnum; +use Symfony\Component\ExpressionLanguage\Tests\Fixtures\FooEnum; use Symfony\Component\ExpressionLanguage\Tests\Fixtures\TestProvider; class ExpressionLanguageTest extends TestCase @@ -78,6 +80,53 @@ public function testConstantFunction() $this->assertEquals('\constant("PHP_VERSION")', $expressionLanguage->compile('constant("PHP_VERSION")')); } + public function testEnumFunctionWithConstantThrows() + { + $this->expectException(\TypeError::class); + $this->expectExceptionMessage('The string "PHP_VERSION" is not the name of a valid enum case.'); + $expressionLanguage = new ExpressionLanguage(); + $expressionLanguage->evaluate('enum("PHP_VERSION")'); + } + + public function testCompiledEnumFunctionWithConstantThrows() + { + $this->expectException(\TypeError::class); + $this->expectExceptionMessage('The string "PHP_VERSION" is not the name of a valid enum case.'); + $expressionLanguage = new ExpressionLanguage(); + eval($expressionLanguage->compile('enum("PHP_VERSION")').';'); + } + + public function testEnumFunction() + { + $expressionLanguage = new ExpressionLanguage(); + $this->assertSame(FooEnum::Foo, $expressionLanguage->evaluate('enum("Symfony\\\\Component\\\\ExpressionLanguage\\\\Tests\\\\Fixtures\\\\FooEnum::Foo")')); + } + + public function testCompiledEnumFunction() + { + $result = null; + $expressionLanguage = new ExpressionLanguage(); + eval(sprintf('$result = %s;', $expressionLanguage->compile('enum("Symfony\\\\Component\\\\ExpressionLanguage\\\\Tests\\\\Fixtures\\\\FooEnum::Foo")'))); + + $this->assertSame(FooEnum::Foo, $result); + } + + public function testBackedEnumFunction() + { + $expressionLanguage = new ExpressionLanguage(); + $this->assertSame(FooBackedEnum::Bar, $expressionLanguage->evaluate('enum("Symfony\\\\Component\\\\ExpressionLanguage\\\\Tests\\\\Fixtures\\\\FooBackedEnum::Bar")')); + $this->assertSame('Foo', $expressionLanguage->evaluate('enum("Symfony\\\\Component\\\\ExpressionLanguage\\\\Tests\\\\Fixtures\\\\FooBackedEnum::Bar").value')); + } + + public function testCompiledEnumFunctionWithBackedEnum() + { + $result = null; + $expressionLanguage = new ExpressionLanguage(); + eval(sprintf('$result = %s;', $expressionLanguage->compile('enum("Symfony\\\\Component\\\\ExpressionLanguage\\\\Tests\\\\Fixtures\\\\FooBackedEnum::Bar")'))); + + $this->assertSame(FooBackedEnum::Bar, $result); + } + public function testProviders() { $expressionLanguage = new ExpressionLanguage(null, [new TestProvider()]); @@ -220,7 +269,7 @@ public function testOperatorCollisions() $expressionLanguage = new ExpressionLanguage(); $expression = 'foo.not in [bar]'; $compiled = $expressionLanguage->compile($expression, ['foo', 'bar']); - $this->assertSame('in_array($foo->not, [0 => $bar])', $compiled); + $this->assertSame('\Symfony\Component\ExpressionLanguage\Node\BinaryNode::inArray($foo->not, [0 => $bar])', $compiled); $result = $expressionLanguage->evaluate($expression, ['foo' => (object) ['not' => 'test'], 'bar' => 'test']); $this->assertTrue($result); diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/FooBackedEnum.php b/src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/FooBackedEnum.php new file mode 100644 index 0000000000000..8bf81231b4dac --- /dev/null +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Fixtures/FooBackedEnum.php @@ -0,0 +1,8 @@ + $input, fn (array $values, $input) => $input), ExpressionFunction::fromPhp('strtoupper'), diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php index 365f07a69ff88..610c6b0dd289b 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\ExpressionLanguage\Tests\Node; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\ExpressionLanguage\Compiler; use Symfony\Component\ExpressionLanguage\Node\ArrayNode; use Symfony\Component\ExpressionLanguage\Node\BinaryNode; @@ -20,6 +21,8 @@ class BinaryNodeTest extends AbstractNodeTestCase { + use ExpectDeprecationTrait; + public static function getEvaluateData(): array { $array = new ArrayNode(); @@ -113,10 +116,10 @@ public static function getCompileData(): array ['pow(5, 2)', new BinaryNode('**', new ConstantNode(5), new ConstantNode(2))], ['("a" . "b")', new BinaryNode('~', new ConstantNode('a'), new ConstantNode('b'))], - ['in_array("a", [0 => "a", 1 => "b"])', new BinaryNode('in', new ConstantNode('a'), $array)], - ['in_array("c", [0 => "a", 1 => "b"])', new BinaryNode('in', new ConstantNode('c'), $array)], - ['!in_array("c", [0 => "a", 1 => "b"])', new BinaryNode('not in', new ConstantNode('c'), $array)], - ['!in_array("a", [0 => "a", 1 => "b"])', new BinaryNode('not in', new ConstantNode('a'), $array)], + ['\Symfony\Component\ExpressionLanguage\Node\BinaryNode::inArray("a", [0 => "a", 1 => "b"])', new BinaryNode('in', new ConstantNode('a'), $array)], + ['\Symfony\Component\ExpressionLanguage\Node\BinaryNode::inArray("c", [0 => "a", 1 => "b"])', new BinaryNode('in', new ConstantNode('c'), $array)], + ['!\Symfony\Component\ExpressionLanguage\Node\BinaryNode::inArray("c", [0 => "a", 1 => "b"])', new BinaryNode('not in', new ConstantNode('c'), $array)], + ['!\Symfony\Component\ExpressionLanguage\Node\BinaryNode::inArray("a", [0 => "a", 1 => "b"])', new BinaryNode('not in', new ConstantNode('a'), $array)], ['range(1, 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))], @@ -214,4 +217,19 @@ public function testCompileMatchesWithInvalidRegexpAsExpression() $node->compile($compiler); eval('$regexp = "this is not a regexp"; '.$compiler->getSource().';'); } + + /** + * @group legacy + */ + public function testInOperatorStrictness() + { + $array = new ArrayNode(); + $array->addElement(new ConstantNode('a')); + $array->addElement(new ConstantNode(true)); + + $node = new BinaryNode('in', new ConstantNode('b'), $array); + + $this->expectDeprecation('Since symfony/expression-language 6.3: The "in" operator will use strict comparisons in Symfony 7.0. Loose match found with key "1" for value "b". Normalize the array parameter so it only has the expected types or implement loose matching in your own expression function.'); + $this->assertTrue($node->evaluate([], [])); + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php index 2ae6f954fd119..aa667f7e4212e 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/FunctionNodeTest.php @@ -41,12 +41,8 @@ public static function getDumpData(): array protected static function getCallables(): array { return [ - 'compiler' => function ($arg) { - return sprintf('foo(%s)', $arg); - }, - 'evaluator' => function ($variables, $arg) { - return $arg; - }, + 'compiler' => fn ($arg) => sprintf('foo(%s)', $arg), + 'evaluator' => fn ($variables, $arg) => $arg, ]; } } diff --git a/src/Symfony/Component/ExpressionLanguage/TokenStream.php b/src/Symfony/Component/ExpressionLanguage/TokenStream.php index bbd1d8ab95789..241725b9c5ddc 100644 --- a/src/Symfony/Component/ExpressionLanguage/TokenStream.php +++ b/src/Symfony/Component/ExpressionLanguage/TokenStream.php @@ -41,6 +41,8 @@ public function __toString(): string /** * Sets the pointer to the next token and returns the old one. + * + * @return void */ public function next() { @@ -55,6 +57,8 @@ public function next() /** * @param string|null $message The syntax error message + * + * @return void */ public function expect(string $type, string $value = null, string $message = null) { diff --git a/src/Symfony/Component/ExpressionLanguage/composer.json b/src/Symfony/Component/ExpressionLanguage/composer.json index 3c8811b0e3a99..4f66a7e2481a6 100644 --- a/src/Symfony/Component/ExpressionLanguage/composer.json +++ b/src/Symfony/Component/ExpressionLanguage/composer.json @@ -17,8 +17,9 @@ ], "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/cache": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "autoload": { "psr-4": { "Symfony\\Component\\ExpressionLanguage\\": "" }, diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 3b3cf7a1c5441..d3280bee51fa9 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -31,6 +31,8 @@ class Filesystem * If the target file is newer, it is overwritten only when the * $overwriteNewerFiles option is set to true. * + * @return void + * * @throws FileNotFoundException When originFile doesn't exist * @throws IOException When copy fails */ @@ -82,6 +84,8 @@ public function copy(string $originFile, string $targetFile, bool $overwriteNewe /** * Creates a directory recursively. * + * @return void + * * @throws IOException On any directory creation failure */ public function mkdir(string|iterable $dirs, int $mode = 0777) @@ -123,6 +127,8 @@ public function exists(string|iterable $files): bool * @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used * @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used * + * @return void + * * @throws IOException When touch fails */ public function touch(string|iterable $files, int $time = null, int $atime = null) @@ -137,6 +143,8 @@ public function touch(string|iterable $files, int $time = null, int $atime = nul /** * Removes files or directories. * + * @return void + * * @throws IOException When removal fails */ public function remove(string|iterable $files) @@ -203,12 +211,14 @@ private static function doRemove(array $files, bool $isRecursive): void * @param int $umask The mode mask (octal) * @param bool $recursive Whether change the mod recursively or not * + * @return void + * * @throws IOException When the change fails */ public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { - if (\is_int($mode) && !self::box('chmod', $file, $mode & ~$umask)) { + if (!self::box('chmod', $file, $mode & ~$umask)) { throw new IOException(sprintf('Failed to chmod file "%s": ', $file).self::$lastError, 0, null, $file); } if ($recursive && is_dir($file) && !is_link($file)) { @@ -223,6 +233,8 @@ public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool * @param string|int $user A user name or number * @param bool $recursive Whether change the owner recursively or not * + * @return void + * * @throws IOException When the change fails */ public function chown(string|iterable $files, string|int $user, bool $recursive = false) @@ -249,6 +261,8 @@ public function chown(string|iterable $files, string|int $user, bool $recursive * @param string|int $group A group nam 10000 e or number * @param bool $recursive Whether change the group recursively or not * + * @return void + * * @throws IOException When the change fails */ public function chgrp(string|iterable $files, string|int $group, bool $recursive = false) @@ -272,6 +286,8 @@ public function chgrp(string|iterable $files, string|int $group, bool $recursive /** * Renames a file or a directory. * + * @return void + * * @throws IOException When target file or directory already exists * @throws IOException When origin cannot be renamed */ @@ -313,6 +329,8 @@ private function isReadable(string $filename): bool /** * Creates a symbolic link or copy a directory. * + * @return void + * * @throws IOException When symlink fails */ public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false) @@ -349,6 +367,8 @@ public function symlink(string $originDir, string $targetDir, bool $copyOnWindow * * @param string|string[] $targetFiles The target file(s) * + * @return void + * * @throws FileNotFoundException When original file is missing or not a file * @throws IOException When link fails, including if link already exists */ @@ -381,7 +401,7 @@ public function hardlink(string $originFile, string|iterable $targetFiles) /** * @param string $linkType Name of the link type, typically 'symbolic' or 'hard' */ - private function linkException(string $origin, string $target, string $linkType) + private function linkException(string $origin, string $target, string $linkType): never { if (self::$lastError) { if ('\\' === \DIRECTORY_SEPARATOR && str_contains(self::$lastError, 'error code(1314)')) { @@ -438,11 +458,9 @@ public function makePathRelative(string $endPath, string $startPath): string $startPath = str_replace('\\', '/', $startPath); } - $splitDriveLetter = function ($path) { - return (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) - ? [substr($path, 2), strtoupper($path[0])] - : [$path, null]; - }; + $splitDriveLetter = fn ($path) => (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) + ? [substr($path, 2), strtoupper($path[0])] + : [$path, null]; $splitPath = function ($path) { $result = []; @@ -508,6 +526,8 @@ public function makePathRelative(string $endPath, string $startPath): string * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false) * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) * + * @return void + * * @throws IOException When file type is unknown */ public function mirror(string $originDir, string $targetDir, \Traversable $iterator = null, array $options = []) @@ -632,6 +652,8 @@ public function tempnam(string $dir, string $prefix, string $suffix = ''): strin * * @param string|resource $content The data to write into the file * + * @return void + * * @throws IOException if the file cannot be written to */ public function dumpFile(string $filename, $content) @@ -671,6 +693,8 @@ public function dumpFile(string $filename, $content) * @param string|resource $content The content to append * @param bool $lock Whether the file should be locked when writing to it * + * @return void + * * @throws IOException If the file is not writable */ public function appendToFile(string $filename, $content/* , bool $lock = false */) @@ -730,7 +754,7 @@ private static function box(string $func, mixed ...$args): mixed /** * @internal */ - public static function handleError(int $type, string $msg) + public static function handleError(int $type, string $msg): void { self::$lastError = $msg; } diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php index 725870c69857a..79cb453ed4f28 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php @@ -23,22 +23,22 @@ class FilesystemTestCase extends TestCase /** * @var Filesystem */ - protected $filesystem = null; + protected $filesystem; /** * @var string */ - protected $workspace = null; + protected $workspace; /** * @var bool|null Flag for hard links on Windows */ - private static $linkOnWindows = null; + private static $linkOnWindows; /** * @var bool|null Flag for symbolic links on Windows */ - private static $symlinkOnWindows = null; + private static $symlinkOnWindows; public static function setUpBeforeClass(): void { diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 7baecdf906260..62c3f9e24f65a 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -396,6 +396,8 @@ public function ignoreVCSIgnored(bool $ignoreVCSIgnored): static * @see ignoreVCS() * * @param string|string[] $pattern VCS patterns to ignore + * + * @return void */ public static function addVCSPattern(string|array $pattern) { @@ -671,9 +673,7 @@ public function getIterator(): \Iterator $iterator = new \AppendIterator(); foreach ($this->dirs as $dir) { - $iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) { - return $this->searchInDirectory($dir); - }))); + $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir)))); } foreach ($this->iterators as $it) { diff --git a/src/Symfony/Component/Finder/Gitignore.php b/src/Symfony/Component/Finder/Gitignore.php index 070074b3ba85c..bf05c5b3793bb 100644 --- a/src/Symfony/Component/Finder/Gitignore.php +++ b/src/Symfony/Component/Finder/Gitignore.php @@ -79,9 +79,7 @@ private static function lineToRegex(string $gitignoreLine): string } $regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~'); - $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', function (array $matches): string { - return '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']'; - }, $regex); + $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', fn (array $matches): string => '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', $regex); $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_NAME_NATURAL === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { if ($a->isDir() && $b->isFile()) { @@ -74,29 +66,19 @@ public function __construct(\Traversable $iterator, int|callable $sort, bool $re return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getATime() - $b->getATime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getATime() - $b->getATime()); } elseif (self::SORT_BY_CHANGED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getCTime() - $b->getCTime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getCTime() - $b->getCTime()); } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getMTime() - $b->getMTime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getMTime() - $b->getMTime()); } elseif (self::SORT_BY_EXTENSION === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strnatcmp($a->getExtension(), $b->getExtension()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getExtension(), $b->getExtension()); } elseif (self::SORT_BY_SIZE === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getSize() - $b->getSize()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getSize() - $b->getSize()); } elseif (self::SORT_BY_NONE === $sort) { $this->sort = $order; } elseif (\is_callable($sort)) { - $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort(...); + $this->sort = $reverseOrder ? static fn (\SplFileInfo $a, \SplFileInfo $b) => -$sort($a, $b) : $sort(...); } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } diff --git a/src/Symfony/Component/Finder/Iterator/VcsIgnoredFilterIterator.php b/src/Symfony/Component/Finder/Iterator/VcsIgnoredFilterIterator.php index 29fc2d99b1af7..7e6051d38971a 100644 --- a/src/Symfony/Component/Finder/Iterator/VcsIgnoredFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/VcsIgnoredFilterIterator.php @@ -126,9 +126,7 @@ private function parentDirectoriesUpTo(string $from, string $upTo): array { return array_filter( $this->parentDirectoriesUpwards($from), - static function (string $directory) use ($upTo): bool { - return str_starts_with($directory, $upTo); - } + static fn (string $directory): bool => str_starts_with($directory, $upTo) ); } diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index cfcec911ec4ee..41dc02713bb76 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -958,7 +958,7 @@ public function testSortByNameCaseInsensitive() public function testSort() { $finder = $this->buildFinder(); - $this->assertSame($finder, $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealPath(), $b->getRealPath()); })); + $this->assertSame($finder, $finder->sort(fn (\SplFileInfo $a, \SplFileInfo $b) => strcmp($a->getRealPath(), $b->getRealPath()))); $this->assertOrderedIterator($this->toAbsolute([ 'Zephire.php', 'foo', @@ -990,12 +990,8 @@ public function testSortAcrossDirectories() ]) ->depth(0) ->files() - ->filter(static function (\SplFileInfo $file): bool { - return '' !== $file->getExtension(); - }) - ->sort(static function (\SplFileInfo $a, \SplFileInfo $b): int { - return strcmp($a->getExtension(), $b->getExtension()) ?: strcmp($a->getFilename(), $b->getFilename()); - }) + ->filter(static fn (\SplFileInfo $file): bool => '' !== $file->getExtension()) + ->sort(static fn (\SplFileInfo $a, \SplFileInfo $b): int => strcmp($a->getExtension(), $b->getExtension()) ?: strcmp($a->getFilename(), $b->getFilename())) ; $this->assertOrderedIterator($this->toAbsolute([ @@ -1018,7 +1014,7 @@ public function testSortAcrossDirectories() public function testFilter() { $finder = $this->buildFinder(); - $this->assertSame($finder, $finder->filter(function (\SplFileInfo $f) { return str_contains($f, 'test'); })); + $this->assertSame($finder, $finder->filter(fn (\SplFileInfo $f) => str_contains($f, 'test'))); $this->assertIterator($this->toAbsolute(['test.php', 'test.py']), $finder->in(self::$tmpDir)->getIterator()); } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php index 3b1f662e2fe05..e3806c42eacd7 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php @@ -36,8 +36,8 @@ public function testAccept($filters, $expected) public static function getAcceptData() { return [ - [[function (\SplFileInfo $fileinfo) { return false; }], []], - [[function (\SplFileInfo $fileinfo) { return str_starts_with($fileinfo, 'test'); }], ['test.php', 'test.py']], + [[fn (\SplFileInfo $fileinfo) => false], []], + [[fn (\SplFileInfo $fileinfo) => str_starts_with($fileinfo, 'test')], ['test.php', 'test.py']], [['is_dir'], []], ]; } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php b/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php index 71db60b48c9e3..e66f9362f09db 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php @@ -19,9 +19,9 @@ protected function assertIterator($expected, \Traversable $iterator) { // set iterator_to_array $use_key to false to avoid values merge // this made FinderTest::testAppendWithAnArray() fail with GnuFinderAdapter - $values = array_map(function (\SplFileInfo $fileinfo) { return str_replace('/', \DIRECTORY_SEPARATOR, $fileinfo->getPathname()); }, iterator_to_array($iterator, false)); + $values = array_map(fn (\SplFileInfo $fileinfo) => str_replace('/', \DIRECTORY_SEPARATOR, $fileinfo->getPathname()), iterator_to_array($iterator, false)); - $expected = array_map(function ($path) { return str_replace('/', \DIRECTORY_SEPARATOR, $path); }, $expected); + $expected = array_map(fn ($path) => str_replace('/', \DIRECTORY_SEPARATOR, $path), $expected); sort($values); sort($expected); @@ -31,8 +31,8 @@ protected function assertIterator($expected, \Traversable $iterator) protected function assertOrderedIterator($expected, \Traversable $iterator) { - $values = array_map(function (\SplFileInfo $fileinfo) { return str_replace('/', \DIRECTORY_SEPARATOR, $fileinfo->getPathname()); }, iterator_to_array($iterator)); - $expected = array_map(function ($path) { return str_replace('/', \DIRECTORY_SEPARATOR, $path); }, $expected); + $values = array_map(fn (\SplFileInfo $fileinfo) => str_replace('/', \DIRECTORY_SEPARATOR, $fileinfo->getPathname()), iterator_to_array($iterator)); + $expected = array_map(fn ($path) => str_replace('/', \DIRECTORY_SEPARATOR, $path), $expected); $this->assertEquals($expected, array_values($values)); } @@ -48,7 +48,7 @@ protected function assertOrderedIterator($expected, \Traversable $iterator) */ protected function assertOrderedIteratorForGroups(array $expected, \Traversable $iterator) { - $values = array_values(array_map(function (\SplFileInfo $fileinfo) { return $fileinfo->getPathname(); }, iterator_to_array($iterator))); + $values = array_values(array_map(fn (\SplFileInfo $fileinfo) => $fileinfo->getPathname(), iterator_to_array($iterator))); foreach ($expected as $subarray) { $temp = []; diff --git a/src/Symfony/Component/Finder/Tests/Iterator/LazyIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/LazyIteratorTest.php index 1a96c32d0b787..a32619cd4a618 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/LazyIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/LazyIteratorTest.php @@ -27,9 +27,7 @@ public function testLazy() public function testDelegate() { - $iterator = new LazyIterator(function () { - return new Iterator(['foo', 'bar']); - }); + $iterator = new LazyIterator(fn () => new Iterator(['foo', 'bar'])); $this->assertCount(2, $iterator); } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php b/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php index 670478d7a7524..5cee84c3b0c73 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php @@ -15,7 +15,7 @@ class MockFileListIterator extends \ArrayIterator { public function __construct(array $filesArray = []) { - $files = array_map(function ($file) { return new MockSplFileInfo($file); }, $filesArray); + $files = array_map(fn ($file) => new MockSplFileInfo($file), $filesArray); parent::__construct($files); } } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php b/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php index 2c23df7fd1785..14bb5e4f3090c 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php @@ -17,11 +17,11 @@ class MockSplFileInfo extends \SplFileInfo public const TYPE_FILE = 2; public const TYPE_UNKNOWN = 3; - private $contents = null; - private $mode = null; - private $type = null; - private $relativePath = null; - private $relativePathname = null; + private $contents; + private $mode; + private $type; + private $relativePath; + private $relativePathname; public function __construct($param) { diff --git a/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php index 5a57055699c3b..32b1a396f8439 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php @@ -267,7 +267,7 @@ public static function getAcceptData() [SortableIterator::SORT_BY_CHANGED_TIME, self::toAbsolute($sortByChangedTime)], [SortableIterator::SORT_BY_MODIFIED_TIME, self::toAbsolute($sortByModifiedTime)], [SortableIterator::SORT_BY_NAME_NATURAL, self::toAbsolute($sortByNameNatural)], - [function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealPath(), $b->getRealPath()); }, self::toAbsolute($customComparison)], + [fn (\SplFileInfo $a, \SplFileInfo $b) => strcmp($a->getRealPath(), $b->getRealPath()), self::toAbsolute($customComparison)], ]; } } diff --git a/src/Symfony/Component/Form/AbstractExtension.php b/src/Symfony/Component/Form/AbstractExtension.php index 5d3c1a8480345..cffca7d398bfd 100644 --- a/src/Symfony/Component/Form/AbstractExtension.php +++ b/src/Symfony/Component/Form/AbstractExtension.php @@ -128,7 +128,7 @@ protected function loadTypeGuesser() * * @throws UnexpectedTypeException if any registered type is not an instance of FormTypeInterface */ - private function initTypes() + private function initTypes(): void { $this->types = []; @@ -147,7 +147,7 @@ private function initTypes() * @throws UnexpectedTypeException if any registered type extension is not * an instance of FormTypeExtensionInterface */ - private function initTypeExtensions() + private function initTypeExtensions(): void { $this->typeExtensions = []; @@ -167,7 +167,7 @@ private function initTypeExtensions() * * @throws UnexpectedTypeException if the type guesser is not an instance of FormTypeGuesserInterface */ - private function initTypeGuesser() + private function initTypeGuesser(): void { $this->typeGuesserLoaded = true; diff --git a/src/Symfony/Component/Form/AbstractRendererEngine.php b/src/Symfony/Component/Form/AbstractRendererEngine.php index f79f1c1338d30..3f1ab79c2669a 100644 --- a/src/Symfony/Component/Form/AbstractRendererEngine.php +++ b/src/Symfony/Component/Form/AbstractRendererEngine.php @@ -61,6 +61,9 @@ public function __construct(array $defaultThemes = []) $this->defaultThemes = $defaultThemes; } + /** + * @return void + */ public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true) { $cacheKey = $view->vars[self::CACHE_KEY_VAR]; diff --git a/src/Symfony/Component/Form/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php index da401930d7833..ad4b1956969da 100644 --- a/src/Symfony/Component/Form/AbstractType.php +++ b/src/Symfony/Component/Form/AbstractType.php @@ -20,27 +20,45 @@ */ abstract class AbstractType implements FormTypeInterface { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { } + /** + * @return void + */ public function finishView(FormView $view, FormInterface $form, array $options) { } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { } + /** + * @return string + */ public function getBlockPrefix() { return StringUtil::fqcnToBlockPrefix(static::class) ?: ''; } + /** + * @return string|null + */ public function getParent() { return FormType::class; diff --git a/src/Symfony/Component/Form/AbstractTypeExtension.php b/src/Symfony/Component/Form/AbstractTypeExtension.php index bcd408c170a19..422f28bf33b85 100644 --- a/src/Symfony/Component/Form/AbstractTypeExtension.php +++ b/src/Symfony/Component/Form/AbstractTypeExtension.php @@ -18,18 +18,30 @@ */ abstract class AbstractTypeExtension implements FormTypeExtensionInterface { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { } + /** + * @return void + */ public function finishView(FormView $view, FormInterface $form, array $options) { } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { } diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index bfba1d6cd0970..20a30968d402e 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -52,6 +52,8 @@ public function __construct(?string $name, array $options = []) /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function add(string|FormBuilderInterface $child, string $type = null, array $options = []): static @@ -62,6 +64,8 @@ public function add(string|FormBuilderInterface $child, string $type = null, arr /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function create(string $name, string $type = null, array $options = []): FormBuilderInterface @@ -72,6 +76,8 @@ public function create(string $name, string $type = null, array $options = []): /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function get(string $name): FormBuilderInterface @@ -82,6 +88,8 @@ public function get(string $name): FormBuilderInterface /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function remove(string $name): static @@ -116,6 +124,8 @@ public function getForm(): Button /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function addEventListener(string $eventName, callable $listener, int $priority = 0): static @@ -126,6 +136,8 @@ public function addEventListener(string $eventName, callable $listener, int $pri /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function addEventSubscriber(EventSubscriberInterface $subscriber): static @@ -136,6 +148,8 @@ public function addEventSubscriber(EventSubscriberInterface $subscriber): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function addViewTransformer(DataTransformerInterface $viewTransformer, bool $forcePrepend = false): static @@ -146,6 +160,8 @@ public function addViewTransformer(DataTransformerInterface $viewTransformer, bo /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function resetViewTransformers(): static @@ -156,6 +172,8 @@ public function resetViewTransformers(): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function addModelTransformer(DataTransformerInterface $modelTransformer, bool $forceAppend = false): static @@ -166,6 +184,8 @@ public function addModelTransformer(DataTransformerInterface $modelTransformer, /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function resetModelTransformers(): static @@ -173,6 +193,9 @@ public function resetModelTransformers(): static throw new BadMethodCallException('Buttons do not support data transformers.'); } + /** + * @return $this + */ public function setAttribute(string $name, mixed $value): static { $this->attributes[$name] = $value; @@ -180,6 +203,9 @@ public function setAttribute(string $name, mixed $value): static return $this; } + /** + * @return $this + */ public function setAttributes(array $attributes): static { $this->attributes = $attributes; @@ -190,6 +216,8 @@ public function setAttributes(array $attributes): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setDataMapper(DataMapperInterface $dataMapper = null): static @@ -216,6 +244,8 @@ public function setDisabled(bool $disabled): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setEmptyData(mixed $emptyData): static @@ -226,6 +256,8 @@ public function setEmptyData(mixed $emptyData): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setErrorBubbling(bool $errorBubbling): static @@ -236,6 +268,8 @@ public function setErrorBubbling(bool $errorBubbling): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setRequired(bool $required): static @@ -246,6 +280,8 @@ public function setRequired(bool $required): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setPropertyPath(string|PropertyPathInterface|null $propertyPath): static @@ -256,6 +292,8 @@ public function setPropertyPath(string|PropertyPathInterface|null $propertyPath) /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setMapped(bool $mapped): static @@ -266,6 +304,8 @@ public function setMapped(bool $mapped): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setByReference(bool $byReference): static @@ -276,6 +316,8 @@ public function setByReference(bool $byReference): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setCompound(bool $compound): static @@ -298,6 +340,8 @@ public function setType(ResolvedFormTypeInterface $type): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setData(mixed $data): static @@ -308,6 +352,8 @@ public function setData(mixed $data): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setDataLocked(bool $locked): static @@ -318,6 +364,8 @@ public function setDataLocked(bool $locked): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setFormFactory(FormFactoryInterface $formFactory) @@ -328,6 +376,8 @@ public function setFormFactory(FormFactoryInterface $formFactory) /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setAction(string $action): static @@ -338,6 +388,8 @@ public function setAction(string $action): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setMethod(string $method): static @@ -348,6 +400,8 @@ public function setMethod(string $method): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setRequestHandler(RequestHandlerInterface $requestHandler): static @@ -374,6 +428,8 @@ public function setAutoInitialize(bool $initialize): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setInheritData(bool $inheritData): static @@ -396,6 +452,8 @@ public function getFormConfig(): FormConfigInterface /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function setIsEmptyCallback(?callable $isEmptyCallback): static @@ -406,6 +464,8 @@ public function setIsEmptyCallback(?callable $isEmptyCallback): static /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function getEventDispatcher(): EventDispatcherInterface @@ -564,6 +624,8 @@ public function getDataLocked(): bool /** * Unsupported method. + * + * @return never */ public function getFormFactory(): FormFactoryInterface { @@ -573,6 +635,8 @@ public function getFormFactory(): FormFactoryInterface /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function getAction(): string @@ -583,6 +647,8 @@ public function getAction(): string /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function getMethod(): string @@ -593,6 +659,8 @@ public function getMethod(): string /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function getRequestHandler(): RequestHandlerInterface @@ -643,6 +711,8 @@ public function getOption(string $name, mixed $default = null): mixed /** * Unsupported method. * + * @return never + * * @throws BadMethodCallException */ public function getIsEmptyCallback(): ?callable diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 68622612e30b3..1ff39c9726070 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +6.3 +--- + + * Don't render seconds for HTML5 date pickers unless "with_seconds" is explicitly set + * Add a `placeholder_attr` option to `ChoiceType` + * Deprecate not configuring the "widget" option of date/time form types, it will default to "single_text" in v7 + 6.2 --- diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php index 524bbf4355cf7..2af19b0bf86b9 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php @@ -64,9 +64,7 @@ public function __construct(iterable $choices, callable $value = null) } if (null === $value && $this->castableToString($choices)) { - $value = function ($choice) { - return false === $choice ? '0' : (string) $choice; - }; + $value = static fn ($choice) => false === $choice ? '0' : (string) $choice; } if (null !== $value) { @@ -74,8 +72,9 @@ public function __construct(iterable $choices, callable $value = null) $this->valueCallback = $value; } else { // Otherwise generate incrementing integers as values - $i = 0; - $value = function () use (&$i) { + $value = static function () { + static $i = 0; + return $i++; }; } @@ -164,7 +163,7 @@ public function getValuesForChoices(array $choices): array * * @internal */ - protected function flatten(array $choices, callable $value, ?array &$choicesByValues, ?array &$keysByValues, ?array &$structuredValues) + protected function flatten(array $choices, callable $value, ?array &$choicesByValues, ?array &$keysByValues, ?array &$structuredValues): void { if (null === $choicesByValues) { $choicesByValues = []; diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php index 62d1b71d9da56..40c0604ea4de8 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php @@ -54,7 +54,7 @@ public static function generateHash(mixed $value, string $namespace = ''): strin if (\is_object($value)) { $value = spl_object_hash($value); } elseif (\is_array($value)) { - array_walk_recursive($value, function (&$v) { + array_walk_recursive($value, static function (&$v) { if (\is_object($v)) { $v = spl_object_hash($v); } @@ -214,6 +214,9 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = return $this->views[$hash]; } + /** + * @return void + */ public function reset() { $this->lists = []; diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index ef3769934167d..fb30fc6ded4cc 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -35,10 +35,9 @@ public function createListFromChoices(iterable $choices, callable $value = null, if ($filter) { // filter the choice list lazily return $this->createListFromLoader(new FilterChoiceLoaderDecorator( - new CallbackChoiceLoader(static function () use ($choices) { - return $choices; - } - ), $filter), $value); + new CallbackChoiceLoader(static fn () => $choices), + $filter + ), $value); } return new ArrayChoiceList($choices, $value); @@ -67,9 +66,7 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC } else { // make sure we have keys that reflect order $preferredChoices = array_values($preferredChoices); - $preferredChoices = static function ($choice) use ($preferredChoices) { - return array_search($choice, $preferredChoices, true); - }; + $preferredChoices = static fn ($choice) => array_search($choice, $preferredChoices, true); } } @@ -137,16 +134,12 @@ public function createView(ChoiceListInterface $list, array|callable $preferredC ); } - uksort($preferredViews, static function ($a, $b) use ($preferredViewsOrder): int { - return isset($preferredViewsOrder[$a], $preferredViewsOrder[$b]) - ? $preferredViewsOrder[$a] <=> $preferredViewsOrder[$b] - : 0; - }); + uksort($preferredViews, static fn ($a, $b) => isset($preferredViewsOrder[$a], $preferredViewsOrder[$b]) ? $preferredViewsOrder[$a] <=> $preferredViewsOrder[$b] : 0); return new ChoiceListView($otherViews, $preferredViews); } - private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) + private static function addChoiceView($choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews): void { // $value may be an integer or a string, since it's stored in the array // keys. We want to guarantee it's a string though. @@ -192,7 +185,7 @@ private static function addChoiceView($choice, string $value, $label, array $key $otherViews[$nextIndex] = $view; } - private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) + private static function addChoiceViewsFromStructuredValues(array $values, $label, array $choices, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews): void { foreach ($values as $key => $value) { if (null === $value) { @@ -246,7 +239,7 @@ private static function addChoiceViewsFromStructuredValues(array $values, $label } } - private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews) + private static function addChoiceViewsGroupedByCallable(callable $groupBy, $choice, string $value, $label, array $keys, &$index, $attr, $labelTranslationParameters, ?callable $isPreferred, array &$preferredViews, array &$preferredViewsOrder, array &$otherViews): void { $groupLabels = $groupBy($choice, $keys[$value], $value); diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php index 2f29bda726cab..fa66290e34485 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -63,13 +63,11 @@ public function createListFromChoices(iterable $choices, mixed $value = null, mi if ($value instanceof PropertyPathInterface) { $accessor = $this->propertyAccessor; - $value = function ($choice) use ($accessor, $value) { - // The callable may be invoked with a non-object/array value - // when such values are passed to - // ChoiceListInterface::getValuesForChoices(). Handle this case - // so that the call to getValue() doesn't break. - return \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null; - }; + // The callable may be invoked with a non-object/array value + // when such values are passed to + // ChoiceListInterface::getValuesForChoices(). Handle this case + // so that the call to getValue() doesn't break. + $value = static fn ($choice) => \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null; } if (\is_string($filter)) { @@ -78,9 +76,7 @@ public function createListFromChoices(iterable $choices, mixed $value = null, mi if ($filter instanceof PropertyPath) { $accessor = $this->propertyAccessor; - $filter = static function ($choice) use ($accessor, $filter) { - return (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter); - }; + $filter = static fn ($choice) => (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter); } return $this->decoratedFactory->createListFromChoices($choices, $value, $filter); @@ -94,13 +90,11 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, mixed $value if ($value instanceof PropertyPathInterface) { $accessor = $this->propertyAccessor; - $value = function ($choice) use ($accessor, $value) { - // The callable may be invoked with a non-object/array value - // when such values are passed to - // ChoiceListInterface::getValuesForChoices(). Handle this case - // so that the call to getValue() doesn't break. - return \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null; - }; + // The callable may be invoked with a non-object/array value + // when such values are passed to + // ChoiceListInterface::getValuesForChoices(). Handle this case + // so that the call to getValue() doesn't break. + $value = static fn ($choice) => \is_object($choice) || \is_array($choice) ? $accessor->getValue($choice, $value) : null; } if (\is_string($filter)) { @@ -109,9 +103,7 @@ public function createListFromLoader(ChoiceLoaderInterface $loader, mixed $value if ($filter instanceof PropertyPath) { $accessor = $this->propertyAccessor; - $filter = static function ($choice) use ($accessor, $filter) { - return (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter); - }; + $filter = static fn ($choice) => (\is_object($choice) || \is_array($choice)) && $accessor->getValue($choice, $filter); } return $this->decoratedFactory->createListFromLoader($loader, $value, $filter); @@ -126,9 +118,7 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = } if ($label instanceof PropertyPathInterface) { - $label = function ($choice) use ($accessor, $label) { - return $accessor->getValue($choice, $label); - }; + $label = static fn ($choice) => $accessor->getValue($choice, $label); } if (\is_string($preferredChoices)) { @@ -136,7 +126,7 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = } if ($preferredChoices instanceof PropertyPathInterface) { - $preferredChoices = function ($choice) use ($accessor, $preferredChoices) { + $preferredChoices = static function ($choice) use ($accessor, $preferredChoices) { try { return $accessor->getValue($choice, $preferredChoices); } catch (UnexpectedTypeException) { @@ -151,9 +141,7 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = } if ($index instanceof PropertyPathInterface) { - $index = function ($choice) use ($accessor, $index) { - return $accessor->getValue($choice, $index); - }; + $index = static fn ($choice) => $accessor->getValue($choice, $index); } if (\is_string($groupBy)) { @@ -161,7 +149,7 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = } if ($groupBy instanceof PropertyPathInterface) { - $groupBy = function ($choice) use ($accessor, $groupBy) { + $groupBy = static function ($choice) use ($accessor, $groupBy) { try { return $accessor->getValue($choice, $groupBy); } catch (UnexpectedTypeException) { @@ -176,9 +164,7 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = } if ($attr instanceof PropertyPathInterface) { - $attr = function ($choice) use ($accessor, $attr) { - return $accessor->getValue($choice, $attr); - }; + $attr = static fn ($choice) => $accessor->getValue($choice, $attr); } if (\is_string($labelTranslationParameters)) { @@ -186,9 +172,7 @@ public function createView(ChoiceListInterface $list, mixed $preferredChoices = } if ($labelTranslationParameters instanceof PropertyPath) { - $labelTranslationParameters = static function ($choice) use ($accessor, $labelTranslationParameters) { - return $accessor->getValue($choice, $labelTranslationParameters); - }; + $labelTranslationParameters = static fn ($choice) => $accessor->getValue($choice, $labelTranslationParameters); } return $this->decoratedFactory->createView( diff --git a/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php b/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php index 453601a6f7221..64fe3baec3829 100644 --- a/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php +++ b/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php @@ -26,7 +26,7 @@ class ChoiceGroupView implements \IteratorAggregate /** * Creates a new choice group view. * - * @param array $choices the choice views in the group + * @param array $choices the choice views in the group */ public function __construct(string $label, array $choices = []) { diff --git a/src/Symfony/Component/Form/Command/DebugCommand.php b/src/Symfony/Component/Form/Command/DebugCommand.php index afa283bbd5f15..4a142e2965e44 100644 --- a/src/Symfony/Component/Form/Command/DebugCommand.php +++ b/src/Symfony/Component/Form/Command/DebugCommand.php @@ -54,6 +54,9 @@ public function __construct(FormRegistryInterface $formRegistry, array $namespac $this->fileLinkFormatter = $fileLinkFormatter; } + /** + * @return void + */ protected function configure() { $this @@ -61,7 +64,7 @@ protected function configure() new InputArgument('class', InputArgument::OPTIONAL, 'The form type class'), new InputArgument('option', InputArgument::OPTIONAL, 'The form type option'), new InputOption('show-deprecated', null, InputOption::VALUE_NONE, 'Display deprecated options in form types'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt or json)', 'txt'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), ]) ->setHelp(<<<'EOF' The %command.name% command displays information about form types. @@ -122,7 +125,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $object = $resolvedType->getOptionsResolver(); if (!$object->isDefined($option)) { - $message = sprintf('Option "%s" is not defined in "%s".', $option, \get_class($resolvedType->getInnerType())); + $message = sprintf('Option "%s" is not defined in "%s".', $option, $resolvedType->getInnerType()::class); if ($alternatives = $this->findAlternatives($option, $object->getDefinedOptions())) { if (1 === \count($alternatives)) { @@ -204,7 +207,7 @@ private function getCoreTypes(): array $coreExtension = new CoreExtension(); $loadTypesRefMethod = (new \ReflectionObject($coreExtension))->getMethod('loadTypes'); $coreTypes = $loadTypesRefMethod->invoke($coreExtension); - $coreTypes = array_map(function (FormTypeInterface $type) { return $type::class; }, $coreTypes); + $coreTypes = array_map(static fn (FormTypeInterface $type) => $type::class, $coreTypes); sort($coreTypes); return $coreTypes; @@ -237,7 +240,7 @@ private function findAlternatives(string $name, array $collection): array } $threshold = 1e3; - $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + $alternatives = array_filter($alternatives, static fn ($lev) => $lev < 2 * $threshold); ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); return array_keys($alternatives); @@ -258,8 +261,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } if ($input->mustSuggestOptionValuesFor('format')) { - $helper = new DescriptorHelper(); - $suggestions->suggestValues($helper->getFormats()); + $suggestions->suggestValues($this->getAvailableFormatOptions()); } } @@ -280,4 +282,9 @@ private function completeOptions(string $class, CompletionSuggestions $suggestio $resolvedType = $this->formRegistry->getType($class); $suggestions->suggestValues($resolvedType->getOptionsResolver()->getDefinedOptions()); } + + private function getAvailableFormatOptions(): array + { + return (new DescriptorHelper())->getFormats(); + } } diff --git a/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php b/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php index 3441d017b15dd..3c54545cf12ac 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php @@ -40,7 +40,7 @@ abstract class Descriptor implements DescriptorInterface protected $parents = []; protected $extensions = []; - public function describe(OutputInterface $output, ?object $object, array $options = []) + public function describe(OutputInterface $output, ?object $object, array $options = []): void { $this->output = $output instanceof OutputStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output); @@ -52,13 +52,13 @@ public function describe(OutputInterface $output, ?object $object, array $option }; } - abstract protected function describeDefaults(array $options); + abstract protected function describeDefaults(array $options): void; - abstract protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = []); + abstract protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = []): void; - abstract protected function describeOption(OptionsResolver $optionsResolver, array $options); + abstract protected function describeOption(OptionsResolver $optionsResolver, array $options): void; - protected function collectOptions(ResolvedFormTypeInterface $type) + protected function collectOptions(ResolvedFormTypeInterface $type): void { $this->parents = []; $this->extensions = []; @@ -96,7 +96,7 @@ protected function collectOptions(ResolvedFormTypeInterface $type) $this->extensions = array_keys($this->extensions); } - protected function getOptionDefinition(OptionsResolver $optionsResolver, string $option) + protected function getOptionDefinition(OptionsResolver $optionsResolver, string $option): array { $definition = []; @@ -139,7 +139,7 @@ protected function getOptionDefinition(OptionsResolver $optionsResolver, string return $definition; } - protected function filterOptionsByDeprecated(ResolvedFormTypeInterface $type) + protected function filterOptionsByDeprecated(ResolvedFormTypeInterface $type): void { $deprecatedOptions = []; $resolver = $type->getOptionsResolver(); @@ -149,7 +149,7 @@ protected function filterOptionsByDeprecated(ResolvedFormTypeInterface $type) } } - $filterByDeprecated = function (array $options) use ($deprecatedOptions) { + $filterByDeprecated = static function (array $options) use ($deprecatedOptions) { foreach ($options as $class => $opts) { if ($deprecated = array_intersect($deprecatedOptions, $opts)) { $options[$class] = $deprecated; @@ -169,7 +169,7 @@ protected function filterOptionsByDeprecated(ResolvedFormTypeInterface $type) private function getParentOptionsResolver(ResolvedFormTypeInterface $type): OptionsResolver { - $this->parents[$class = \get_class($type->getInnerType())] = []; + $this->parents[$class = $type->getInnerType()::class] = []; if (null !== $type->getParent()) { $optionsResolver = clone $this->getParentOptionsResolver($type->getParent()); @@ -186,7 +186,7 @@ private function getParentOptionsResolver(ResolvedFormTypeInterface $type): Opti return $optionsResolver; } - private function collectTypeExtensionsOptions(ResolvedFormTypeInterface $type, OptionsResolver $optionsResolver) + private function collectTypeExtensionsOptions(ResolvedFormTypeInterface $type, OptionsResolver $optionsResolver): void { foreach ($type->getTypeExtensions() as $extension) { $inheritedOptions = $optionsResolver->getDefinedOptions(); diff --git a/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php index d40561e468eb3..1f5c7bfa55fb3 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php @@ -21,7 +21,7 @@ */ class JsonDescriptor extends Descriptor { - protected function describeDefaults(array $options) + protected function describeDefaults(array $options): void { $data['builtin_form_types'] = $options['core_types']; $data['service_form_types'] = $options['service_types']; @@ -33,7 +33,7 @@ protected function describeDefaults(array $options) $this->writeData($data, $options); } - protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = []) + protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = []): void { $this->collectOptions($resolvedFormType); @@ -51,7 +51,7 @@ protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedF $this->sortOptions($formOptions); $data = [ - 'class' => \get_class($resolvedFormType->getInnerType()), + 'class' => $resolvedFormType->getInnerType()::class, 'block_prefix' => $resolvedFormType->getInnerType()->getBlockPrefix(), 'options' => $formOptions, 'parent_types' => $this->parents, @@ -61,7 +61,7 @@ protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedF $this->writeData($data, $options); } - protected function describeOption(OptionsResolver $optionsResolver, array $options) + protected function describeOption(OptionsResolver $optionsResolver, array $options): void { $definition = $this->getOptionDefinition($optionsResolver, $options['option']); @@ -93,14 +93,14 @@ protected function describeOption(OptionsResolver $optionsResolver, array $optio $this->writeData($data, $options); } - private function writeData(array $data, array $options) + private function writeData(array $data, array $options): void { $flags = $options['json_encoding'] ?? 0; $this->output->write(json_encode($data, $flags | \JSON_PRETTY_PRINT)."\n"); } - private function sortOptions(array &$options) + private function sortOptions(array &$options): void { foreach ($options as &$opts) { $sorted = false; diff --git a/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php index 439526af75b35..c4a2db27a0810 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php @@ -31,13 +31,11 @@ public function __construct(FileLinkFormatter $fileLinkFormatter = null) $this->fileLinkFormatter = $fileLinkFormatter; } - protected function describeDefaults(array $options) + protected function describeDefaults(array $options): void { if ($options['core_types']) { $this->output->section('Built-in form types (Symfony\Component\Form\Extension\Core\Type)'); - $shortClassNames = array_map(function ($fqcn) { - return $this->formatClassLink($fqcn, \array_slice(explode('\\', $fqcn), -1)[0]); - }, $options['core_types']); + $shortClassNames = array_map(fn ($fqcn) => $this->formatClassLink($fqcn, \array_slice(explode('\\', $fqcn), -1)[0]), $options['core_types']); for ($i = 0, $loopsMax = \count($shortClassNames); $i * 5 < $loopsMax; ++$i) { $this->output->writeln(' '.implode(', ', \array_slice($shortClassNames, $i * 5, 5))); } @@ -61,7 +59,7 @@ protected function describeDefaults(array $options) } } - protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = []) + protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = []): void { $this->collectOptions($resolvedFormType); @@ -84,7 +82,7 @@ protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedF 'extension' => 'Extension options', ], $formOptions); - $this->output->title(sprintf('%s (Block prefix: "%s")', \get_class($resolvedFormType->getInnerType()), $resolvedFormType->getInnerType()->getBlockPrefix())); + $this->output->title(sprintf('%s (Block prefix: "%s")', $resolvedFormType->getInnerType()::class, $resolvedFormType->getInnerType()->getBlockPrefix())); if ($formOptions) { $this->output->table($tableHeaders, $this->buildTableRows($tableHeaders, $formOptions)); @@ -101,7 +99,7 @@ protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedF } } - protected function describeOption(OptionsResolver $optionsResolver, array $options) + protected function describeOption(OptionsResolver $optionsResolver, array $options): void { $definition = $this->getOptionDefinition($optionsResolver, $options['option']); @@ -135,7 +133,7 @@ protected function describeOption(OptionsResolver $optionsResolver, array $optio } array_pop($rows); - $this->output->title(sprintf('%s (%s)', \get_class($options['type']), $options['option'])); + $this->output->title(sprintf('%s (%s)', $options['type']::class, $options['option'])); $this->output->table([], $rows); } diff --git a/src/Symfony/Component/Form/DataMapperInterface.php b/src/Symfony/Component/Form/DataMapperInterface.php index b668de3f35ebb..f04137aec64e4 100644 --- a/src/Symfony/Component/Form/DataMapperInterface.php +++ b/src/Symfony/Component/Form/DataMapperInterface.php @@ -22,8 +22,10 @@ interface DataMapperInterface * The method is responsible for calling {@link FormInterface::setData()} * on the children of compound forms, defining their underlying model data. * - * @param mixed $viewData View data of the compound form being initialized - * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances + * @param mixed $viewData View data of the compound form being initialized + * @param \Traversable $forms A list of {@link FormInterface} instances + * + * @return void * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ @@ -52,9 +54,11 @@ public function mapDataToForms(mixed $viewData, \Traversable $forms); * The model data can be an array or an object, so this second argument is always passed * by reference. * - * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances - * @param mixed $viewData The compound form's view data that get mapped - * its children model data + * @param \Travers 10000 able $forms A list of {@link FormInterface} instances + * @param mixed &$viewData The compound form's view data that get mapped + * its children model data + * + * @return void * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ diff --git a/src/Symfony/Component/Form/DependencyInjection/FormPass.php b/src/Symfony/Component/Form/DependencyInjection/FormPass.php index cba1aeeb2cde6..efb6d5c8bb218 100644 --- a/src/Symfony/Component/Form/DependencyInjection/FormPass.php +++ b/src/Symfony/Component/Form/DependencyInjection/FormPass.php @@ -30,6 +30,9 @@ class FormPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('form.extension')) { diff --git a/src/Symfony/Component/Form/Extension/Core/DataAccessor/PropertyPathAccessor.php b/src/Symfony/Component/Form/Extension/Core/DataAccessor/PropertyPathAccessor.php index 85efa150f4fb7..e06f583cbd5a3 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataAccessor/PropertyPathAccessor.php +++ b/src/Symfony/Component/Form/Extension/Core/DataAccessor/PropertyPathAccessor.php @@ -74,7 +74,7 @@ public function isWritable(object|array $data, FormInterface $form): bool return null !== $form->getPropertyPath(); } - private function getPropertyValue(object|array $data, PropertyPathInterface $propertyPath) + private function getPropertyValue(object|array $data, PropertyPathInterface $propertyPath): mixed { try { return $this->propertyAccessor->getValue($data, $propertyPath); diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php index 2345138112d80..119c81107d2a4 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php @@ -25,6 +25,9 @@ */ class CheckboxListMapper implements DataMapperInterface { + /** + * @return void + */ public function mapDataToForms(mixed $choices, \Traversable $checkboxes) { if (!\is_array($choices ??= [])) { @@ -37,6 +40,9 @@ public function mapDataToForms(mixed $choices, \Traversable $checkboxes) } } + /** + * @return void + */ public function mapFormsToData(\Traversable $checkboxes, mixed &$choices) { if (!\is_array($choices)) { diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php index 4ae0fc3c8f738..37fdba0c35890 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php @@ -25,6 +25,9 @@ */ class RadioListMapper implements DataMapperInterface { + /** + * @return void + */ public function mapDataToForms(mixed $choice, \Traversable $radios) { if (!\is_string($choice)) { @@ -37,6 +40,9 @@ public function mapDataToForms(mixed $choice, \Traversable $radios) } } + /** + * @return void + */ public function mapFormsToData(\Traversable $radios, mixed &$choice) { if (null !== $choice && !\is_string($choice)) { diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php index 40cd1b89df64f..a59ad9cb72aa3 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php @@ -23,6 +23,12 @@ class DateTimeToHtml5LocalDateTimeTransformer extends BaseDateTimeTransformer { public const HTML5_FORMAT = 'Y-m-d\\TH:i:s'; + public const HTML5_FORMAT_NO_SECONDS = 'Y-m-d\\TH:i'; + + public function __construct(string $inputTimezone = null, string $outputTimezone = null, private bool $withSeconds = false) + { + parent::__construct($inputTimezone, $outputTimezone); + } /** * Transforms a \DateTime into a local date and time string. @@ -54,7 +60,7 @@ public function transform(mixed $dateTime): string $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); } - return $dateTime->format(self::HTML5_FORMAT); + return $dateTime->format($this->withSeconds ? self::HTML5_FORMAT : self::HTML5_FORMAT_NO_SECONDS); } /** diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php index e9288da415ffe..7189977549121 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php @@ -32,6 +32,9 @@ public function __construct(?string $defaultProtocol = 'http') $this->defaultProtocol = $defaultProtocol; } + /** + * @return void + */ public function onSubmit(FormEvent $event) { $data = $event->getData(); diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php index 4f77342f907ee..62cd0a42a79f9 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php @@ -41,6 +41,9 @@ public static function getSubscribedEvents(): array ]; } + /** + * @return void + */ public function onSubmit(FormEvent $event) { $dataToMergeInto = $event->getForm()->getNormData(); diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php index f68d5b6d2c9c5..cec439754e20f 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php @@ -52,6 +52,9 @@ public static function getSubscribedEvents(): array ]; } + /** + * @return void + */ public function preSetData(FormEvent $event) { $form = $event->getForm(); @@ -74,6 +77,9 @@ public function preSetData(FormEvent $event) } } + /** + * @return void + */ public function preSubmit(FormEvent $event) { $form = $event->getForm(); @@ -104,6 +110,9 @@ public function preSubmit(FormEvent $event) } } + /** + * @return void + */ public function onSubmit(FormEvent $event) { $form = $event->getForm(); diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php index 090a27158d03d..c9c216b59f437 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/TransformationFailureListener.php @@ -36,6 +36,9 @@ public static function getSubscribedEvents(): array ]; } + /** + * @return void + */ public function convertTransformationFailureToFormError(FormEvent $event) { $form = $event->getForm(); diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php index be763c78d2d72..81a55f3cb0e92 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php @@ -23,6 +23,9 @@ */ class TrimListener implements EventSubscriberInterface { + /** + * @return void + */ public function preSubmit(FormEvent $event) { $data = $event->getData(); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php index 0bc3ebe94399b..5e2ae224816fa 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php @@ -29,12 +29,18 @@ */ abstract class BaseType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->setDisabled($options['disabled']); $builder->setAutoInitialize($options['auto_initialize']); } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $name = $form->getName(); @@ -119,6 +125,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) ]); } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php b/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php index 1be317ebd3d23..fa60d016eb64a 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php @@ -16,6 +16,9 @@ class BirthdayType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php index ee39cee56b413..d710546407c94 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php @@ -31,6 +31,9 @@ public function getBlockPrefix(): string return 'button'; } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { parent::configureOptions($resolver); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php index d513694a4ccf8..291ede93ef3ec 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php @@ -20,6 +20,9 @@ class CheckboxType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { // Unlike in other types, where the data is NULL by default, it @@ -32,6 +35,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder->addViewTransformer(new BooleanToStringTransformer($options['value'], $options['false_values'])); } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars = array_replace($view->vars, [ @@ -40,11 +46,12 @@ public function buildView(FormView $view, FormInterface $form, array $options) ]); } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { - $emptyData = function (FormInterface $form, $viewData) { - return $viewData; - }; + $emptyData = static fn (FormInterface $form, $viewData) => $viewData; $resolver->setDefaults([ 'value' => '1', @@ -52,9 +59,7 @@ public function configureOptions(OptionsResolver $resolver) 'compound' => false, 'false_values' => [null], 'invalid_message' => 'The checkbox has an invalid value.', - 'is_empty_callback' => static function ($modelData): bool { - return false === $modelData; - }, + 'is_empty_callback' => static fn ($modelData): bool => false === $modelData, ]); $resolver->setAllowedTypes('false_values', 'array'); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 2dde39e8af9ef..4dcd3b687753e 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -62,6 +62,9 @@ public function __construct(ChoiceListFactoryInterface $choiceListFactory = null $this->translator = $translator; } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $unknownValues = []; @@ -84,7 +87,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) // Check if the choices already contain the empty value // Only add the placeholder option if this is not the case if (null !== $options['placeholder'] && 0 === \count($choiceList->getChoicesForValues(['']))) { - $placeholderView = new ChoiceView(null, '', $options['placeholder']); + $placeholderView = new ChoiceView(null, '', $options['placeholder'], $options['placeholder_attr']); // "placeholder" is a reserved name $this->addSubForm($builder, 'placeholder', $placeholderView, $options); @@ -97,7 +100,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) if ($options['expanded'] || $options['multiple']) { // Make sure that scalar, submitted values are converted to arrays // which can be submitted to the checkboxes/radio buttons - $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($choiceList, $options, &$unknownValues) { + $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($choiceList, $options, &$unknownValues) { $form = $event->getForm(); $data = $event->getData(); @@ -144,11 +147,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } } else { - foreach ($data as $value) { - if ($choiceList->getChoicesForValues([$value])) { - $knownValues[] = $value; - unset($unknownValues[$value]); - } + foreach ($choiceList->getChoicesForValues($data) as $index => $choice) { + $value = $data[$index]; + $knownValues[] = $value; + unset($unknownValues[$value]); } } @@ -167,16 +169,17 @@ public function buildForm(FormBuilderInterface $builder, array $options) if ($options['multiple']) { $messageTemplate = $options['invalid_message'] ?? 'The value {{ value }} is not valid.'; + $translator = $this->translator; - $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$unknownValues, $messageTemplate) { + $builder->addEventListener(FormEvents::POST_SUBMIT, static function (FormEvent $event) use (&$unknownValues, $messageTemplate, $translator) { // Throw exception if unknown values were submitted if (\count($unknownValues) > 0) { $form = $event->getForm(); $clientDataAsString = \is_scalar($form->getViewData()) ? (string) $form->getViewData() : (\is_array($form->getViewData()) ? implode('", "', array_keys($unknownValues)) : \gettype($form->getViewData())); - if (null !== $this->translator) { - $message = $this->translator->trans($messageTemplate, ['{{ value }}' => $clientDataAsString], 'validators'); + if ($translator) { + $message = $translator->trans($messageTemplate, ['{{ value }}' => $clientDataAsString], 'validators'); } else { $message = strtr($messageTemplate, ['{{ value }}' => $clientDataAsString]); } @@ -200,7 +203,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) // To avoid issues when the submitted choices are arrays (i.e. array to string conversions), // we have to ensure that all elements of the submitted choice data are NULL, strings or ints. - $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { + $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) { $data = $event->getData(); if (!\is_array($data)) { @@ -215,6 +218,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) }, 256); } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $choiceTranslationDomain = $options['choice_translation_domain']; @@ -237,6 +243,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) 'choices' => $choiceListView->choices, 'separator' => '-------------------', 'placeholder' => null, + 'placeholder_attr' => [], 'choice_translation_domain' => $choiceTranslationDomain, 'choice_translation_parameters' => $options['choice_translation_parameters'], ]); @@ -246,13 +253,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) // closure here that is optimized for the value of the form, to // avoid making the type check inside the closure. if ($options['multiple']) { - $view->vars['is_selected'] = function ($choice, array $values) { - return \in_array($choice, $values, true); - }; + $view->vars['is_selected'] = static fn ($choice, array $values) => \in_array($choice, $values, true); } else { - $view->vars['is_selected'] = function ($choice, $value) { - return $choice === $value; - }; + $view->vars['is_selected'] = static fn ($choice, $value) => $choice === $value; } // Check if the choices already contain the empty value @@ -261,6 +264,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) // Only add the empty value option if this is not the case if (null !== $options['placeholder'] && !$view->vars['placeholder_in_choices']) { $view->vars['placeholder'] = $options['placeholder']; + $view->vars['placeholder_attr'] = $options['placeholder_attr']; } if ($options['multiple'] && !$options['expanded']) { @@ -271,6 +275,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function finishView(FormView $view, FormInterface $form, array $options) { if ($options['expanded']) { @@ -288,9 +295,12 @@ public function finishView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { - $emptyData = function (Options $options) { + $emptyData = static function (Options $options) { if ($options['expanded'] && !$options['multiple']) { return null; } @@ -302,11 +312,9 @@ public function configureOptions(OptionsResolver $resolver) return ''; }; - $placeholderDefault = function (Options $options) { - return $options['required'] ? null : ''; - }; + $placeholderDefault = static fn (Options $options) => $options['required'] ? null : ''; - $placeholderNormalizer = function (Options $options, $placeholder) { + $placeholderNormalizer = static function (Options $options, $placeholder) { if ($options['multiple']) { // never use an empty value for this case return null; @@ -325,11 +333,9 @@ public function configureOptions(OptionsResolver $resolver) return $placeholder; }; - $compound = function (Options $options) { - return $options['expanded']; - }; + $compound = static fn (Options $options) => $options['expanded']; - $choiceTranslationDomainNormalizer = function (Options $options, $choiceTranslationDomain) { + $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) { if (true === $choiceTranslationDomain) { return $options['translation_domain']; } @@ -352,6 +358,7 @@ public function configureOptions(OptionsResolver $resolver) 'group_by' => null, 'empty_data' => $emptyData, 'placeholder' => $placeholderDefault, + 'placeholder_attr' => [], 'error_bubbling' => false, 'compound' => $compound, // The view data is always a string or an array of strings, @@ -375,6 +382,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('choice_value', ['null', 'callable', 'string', PropertyPath::class, ChoiceValue::class]); $resolver->setAllowedTypes('choice_attr', ['null', 'array', 'callable', 'string', PropertyPath::class, ChoiceAttr::class]); $resolver->setAllowedTypes('choice_translation_parameters', ['null', 'array', 'callable', ChoiceTranslationParameters::class]); + $resolver->setAllowedTypes('placeholder_attr', ['array']); $resolver->setAllowedTypes('preferred_choices', ['array', \Traversable::class, 'callable', 'string', PropertyPath::class, PreferredChoice::class]); $resolver->setAllowedTypes('group_by', ['null', 'callable', 'string', PropertyPath::class, GroupBy::class]); } @@ -387,7 +395,7 @@ public function getBlockPrefix(): string /** * Adds the sub fields for an expanded choice field. */ - private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options) + private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options): void { foreach ($choiceViews as $name => $choiceView) { // Flatten groups @@ -405,7 +413,7 @@ private function addSubForms(FormBuilderInterface $builder, array $choiceViews, } } - private function addSubForm(FormBuilderInterface $builder, string $name, ChoiceView $choiceView, array $options) + private function addSubForm(FormBuilderInterface $builder, string $name, ChoiceView $choiceView, array $options): void { $choiceOpts = [ 'value' => $choiceView->value, @@ -429,7 +437,7 @@ private function addSubForm(FormBuilderInterface $builder, string $name, ChoiceV $builder->add($name, $choiceType, $choiceOpts); } - private function createChoiceList(array $options) + private function createChoiceList(array $options): ChoiceListInterface { if (null !== $options['choice_loader']) { return $this->choiceListFactory->createListFromLoader( @@ -449,7 +457,7 @@ private function createChoiceList(array $options) ); } - private function createChoiceListView(ChoiceListInterface $choiceList, array $options) + private function createChoiceListView(ChoiceListInterface $choiceList, array $options): ChoiceListView { return $this->choiceListFactory->createView( $choiceList, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php index 6e4d0381de21b..0216e61dd5dad 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php @@ -21,6 +21,9 @@ class CollectionType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $resizePrototypeOptions = null; @@ -51,6 +54,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder->addEventSubscriber($resizeListener); } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars = array_replace($view->vars, [ @@ -64,6 +70,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function finishView(FormView $view, FormInterface $form, array $options) { $prefixOffset = -2; @@ -95,9 +104,12 @@ public function finishView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { - $entryOptionsNormalizer = function (Options $options, $value) { + $entryOptionsNormalizer = static function (Options $options, $value) { $value['block_name'] = 'entry'; return $value; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php b/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php index 26d670634bb2f..31538fc3c7c48 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ColorType.php @@ -33,13 +33,17 @@ public function __construct(TranslatorInterface $translator = null) $this->translator = $translator; } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { if (!$options['html5']) { return; } - $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event): void { + $translator = $this->translator; + $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($translator): void { $value = $event->getData(); if (null === $value || '' === $value) { return; @@ -53,12 +57,15 @@ public function buildForm(FormBuilderInterface $builder, array $options) $messageParameters = [ '{{ value }}' => \is_scalar($value) ? (string) $value : \gettype($value), ]; - $message = $this->translator ? $this->translator->trans($messageTemplate, $messageParameters, 'validators') : $messageTemplate; + $message = $translator?->trans($messageTemplate, $messageParameters, 'validators') ?? $messageTemplate; $event->getForm()->addError(new FormError($message, $messageTemplate, $messageParameters)); }); } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php index 25aa652f5cc72..6f872660a0c65 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php @@ -22,6 +22,9 @@ class CountryType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ @@ -33,9 +36,7 @@ public function configureOptions(OptionsResolver $resolver) $choiceTranslationLocale = $options['choice_translation_locale']; $alpha3 = $options['alpha3']; - return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale, $alpha3) { - return array_flip($alpha3 ? Countries::getAlpha3Names($choiceTranslationLocale) : Countries::getNames($choiceTranslationLocale)); - }), [$choiceTranslationLocale, $alpha3]); + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => array_flip($alpha3 ? Countries::getAlpha3Names($choiceTranslationLocale) : Countries::getNames($choiceTranslationLocale))), [$choiceTranslationLocale, $alpha3]); }, 'choice_translation_domain' => false, 'choice_translation_locale' => null, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php index 435bc157091bc..89edc6f6309d3 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php @@ -22,6 +22,9 @@ class CurrencyType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ @@ -32,9 +35,7 @@ public function configureOptions(OptionsResolver $resolver) $choiceTranslationLocale = $options['choice_translation_locale']; - return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) { - return array_flip(Currencies::getNames($choiceTranslationLocale)); - }), $choiceTranslationLocale); + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => array_flip(Currencies::getNames($choiceTranslationLocale))), $choiceTranslationLocale); }, 'choice_translation_domain' => false, 'choice_translation_locale' => null, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php index e745c1af045ee..655ef6682f691 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php @@ -43,6 +43,9 @@ class DateIntervalType extends AbstractType 'choice' => ChoiceType::class, ]; + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { if (!$options['with_years'] && !$options['with_months'] && !$options['with_weeks'] && !$options['with_days'] && !$options['with_hours'] && !$options['with_minutes'] && !$options['with_seconds']) { @@ -145,6 +148,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $vars = [ @@ -157,20 +163,17 @@ public function buildView(FormView $view, FormInterface $form, array $options) $view->vars = array_replace($view->vars, $vars); } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { - $compound = function (Options $options) { - return 'single_text' !== $options['widget']; - }; - $emptyData = function (Options $options) { - return 'single_text' === $options['widget'] ? '' : []; - }; + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; + $emptyData = static fn (Options $options) => 'single_text' === $options['widget'] ? '' : []; - $placeholderDefault = function (Options $options) { - return $options['required'] ? null : ''; - }; + $placeholderDefault = static fn (Options $options) => $options['required'] ? null : ''; - $placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) { + $placeholderNormalizer = static function (Options $options, $placeholder) use ($placeholderDefault) { if (\is_array($placeholder)) { $default = $placeholderDefault($options); @@ -180,20 +183,16 @@ public function configureOptions(OptionsResolver $resolver) return array_fill_keys(self::TIME_PARTS, $placeholder); }; - $labelsNormalizer = function (Options $options, array $labels) { - return array_replace([ - 'years' => null, - 'months' => null, - 'days' => null, - 'weeks' => null, - 'hours' => null, - 'minutes' => null, - 'seconds' => null, - 'invert' => 'Negative interval', - ], array_filter($labels, function ($label) { - return null !== $label; - })); - }; + $labelsNormalizer = static fn (Options $options, array $labels) => array_replace([ + 'years' => null, + 'months' => null, + 'days' => null, + 'weeks' => null, + 'hours' => null, + 'minutes' => null, + 'seconds' => null, + 'invert' => 'Negative interval', + ], array_filter($labels, static fn ($label) => null !== $label)); $resolver->setDefaults([ 'with_years' => true, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index 35022530e6873..32c58447cdfb2 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -47,6 +47,9 @@ class DateTimeType extends AbstractType \IntlDateFormatter::SHORT, ]; + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $parts = ['year', 'month', 'day', 'hour']; @@ -76,7 +79,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) if (self::HTML5_FORMAT === $pattern) { $builder->addViewTransformer(new DateTimeToHtml5LocalDateTimeTransformer( $options['model_timezone'], - $options['view_timezone'] + $options['view_timezone'], + $options['with_seconds'] )); } else { $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer( @@ -107,12 +111,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) ])); if ($emptyData instanceof \Closure) { - $lazyEmptyData = static function ($option) use ($emptyData) { - return static function (FormInterface $form) use ($emptyData, $option) { - $emptyData = $emptyData($form->getParent()); + $lazyEmptyData = static fn ($option) => static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); - return $emptyData[$option] ?? ''; - }; + return $emptyData[$option] ?? ''; }; $dateOptions['empty_data'] = $lazyEmptyData('date'); @@ -146,18 +148,13 @@ public function buildForm(FormBuilderInterface $builder, array $options) $timeOptions['label'] = false; } - if (null !== $options['date_widget']) { - $dateOptions['widget'] = $options['date_widget']; - } + $dateOptions['widget'] = $options['date_widget'] ?? $options['widget'] ?? 'choice'; + $timeOptions['widget'] = $options['time_widget'] ?? $options['widget'] ?? 'choice'; if (null !== $options['date_label']) { $dateOptions['label'] = $options['date_label']; } - if (null !== $options['time_widget']) { - $timeOptions['widget'] = $options['time_widget']; - } - if (null !== $options['time_label']) { $timeOptions['label'] = $options['time_label']; } @@ -199,6 +196,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['widget'] = $options['widget']; @@ -214,27 +214,22 @@ public function buildView(FormView $view, FormInterface $form, array $options) // adding the HTML attribute step if not already defined. // Otherwise the browser will not display and so not send the seconds // therefore the value will always be considered as invalid. - if ($options['with_seconds'] && !isset($view->vars['attr']['step'])) { - $view->vars['attr']['step'] = 1; + if (!isset($view->vars['attr']['step'])) { + if ($options['with_seconds']) { + $view->vars['attr']['step'] = 1; + } elseif (!$options['with_minutes']) { + $view->vars['attr']['step'] = 3600; + } } } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { - $compound = function (Options $options) { - return 'single_text' !== $options['widget']; - }; - - // Defaults to the value of "widget" - $dateWidget = function (Options $options) { - return 'single_text' === $options['widget'] ? null : $options['widget']; - }; - - // Defaults to the value of "widget" - $timeWidget = function (Options $options) { - return 'single_text' === $options['widget'] ? null : $options['widget']; - }; + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; $resolver->setDefaults([ 'input' => 'datetime', @@ -243,8 +238,8 @@ public function configureOptions(OptionsResolver $resolver) 'format' => self::HTML5_FORMAT, 'date_format' => null, 'widget' => null, - 'date_widget' => $dateWidget, - 'time_widget' => $timeWidget, + 'date_widget' => null, + 'time_widget' => null, 'with_minutes' => true, 'with_seconds' => false, 'html5' => true, @@ -260,9 +255,7 @@ public function configureOptions(OptionsResolver $resolver) 'compound' => $compound, 'date_label' => null, 'time_label' => null, - 'empty_data' => function (Options $options) { - return $options['compound'] ? [] : ''; - }, + 'empty_data' => static fn (Options $options) => $options['compound'] ? [] : '', 'input_format' => 'Y-m-d H:i:s', 'invalid_message' => 'Please enter a valid date and time.', ]); @@ -309,28 +302,29 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('input_format', 'string'); - $resolver->setNormalizer('date_format', function (Options $options, $dateFormat) { + $resolver->setNormalizer('date_format', static function (Options $options, $dateFormat) { if (null !== $dateFormat && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) { throw new LogicException(sprintf('Cannot use the "date_format" option of the "%s" with an HTML5 date.', self::class)); } return $dateFormat; }); - $resolver->setNormalizer('date_widget', function (Options $options, $dateWidget) { - if (null !== $dateWidget && 'single_text' === $options['widget']) { - throw new LogicException(sprintf('Cannot use the "date_widget" option of the "%s" when the "widget" option is set to "single_text".', self::class)); - } - - return $dateWidget; - }); - $resolver->setNormalizer('time_widget', function (Options $options, $timeWidget) { - if (null !== $timeWidget && 'single_text' === $options['widget']) { - throw new LogicException(sprintf('Cannot use the "time_widget" option of the "%s" when the "widget" option is set to "single_text".', self::class)); + $resolver->setNormalizer('widget', static function (Options $options, $widget) { + if ('single_text' === $widget) { + if (null !== $options['date_widget']) { + throw new LogicException(sprintf('Cannot use the "date_widget" option of the "%s" when the "widget" option is set to "single_text".', self::class)); + } + if (null !== $options['time_widget']) { + throw new LogicException(sprintf('Cannot use the "time_widget" option of the "%s" when the "widget" option is set to "single_text".', self::class)); + } + } elseif (null === $widget && null === $options['date_widget'] && null === $options['time_widget']) { + trigger_deprecation('symfony/form', '6.3', 'Not configuring the "widget" option of form type "datetime" is deprecated. It will default to "single_text" in Symfony 7.0.'); + // return 'single_text'; } - return $timeWidget; + return $widget; }); - $resolver->setNormalizer('html5', function (Options $options, $html5) { + $resolver->setNormalizer('html5', static function (Options $options, $html5) { if ($html5 && self::HTML5_FORMAT !== $options['format']) { throw new LogicException(sprintf('Cannot use the "format" option of "%s" when the "html5" option is enabled.', self::class)); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index d88819ebd673e..3b68dc241af59 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -43,6 +43,9 @@ class DateType extends AbstractType 'choice' => ChoiceType::class, ]; + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $dateFormat = \is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT; @@ -81,12 +84,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) $emptyData = $builder->getEmptyData() ?: []; if ($emptyData instanceof \Closure) { - $lazyEmptyData = static function ($option) use ($emptyData) { - return static function (FormInterface $form) use ($emptyData, $option) { - $emptyData = $emptyData($form->getParent()); + $lazyEmptyData = static fn ($option) => static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); - return $emptyData[$option] ?? ''; - }; + return $emptyData[$option] ?? ''; }; $yearOptions['empty_data'] = $lazyEmptyData('year'); @@ -179,6 +180,9 @@ class_exists(\IntlTimeZone::class, false) ? \IntlTimeZone::createDefault() : nul } } + /** + * @return void + */ public function finishView(FormView $view, FormInterface $form, array $options) { $view->vars['widget'] = $options['widget']; @@ -216,17 +220,16 @@ public function finishView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { - $compound = function (Options $options) { - return 'single_text' !== $options['widget']; - }; + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; - $placeholderDefault = function (Options $options) { - return $options['required'] ? null : ''; - }; + $placeholderDefault = static fn (Options $options) => $options['required'] ? null : ''; - $placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) { + $placeholderNormalizer = static function (Options $options, $placeholder) use ($placeholderDefault) { if (\is_array($placeholder)) { $default = $placeholderDefault($options); @@ -243,7 +246,7 @@ public function configureOptions(OptionsResolver $resolver) ]; }; - $choiceTranslationDomainNormalizer = function (Options $options, $choiceTranslationDomain) { + $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) { if (\is_array($choiceTranslationDomain)) { $default = false; @@ -260,15 +263,17 @@ public function configureOptions(OptionsResolver $resolver) ]; }; - $format = function (Options $options) { - return 'single_text' === $options['widget'] ? self::HTML5_FORMAT : self::DEFAULT_FORMAT; - }; + $format = static fn (Options $options) => 'single_text' === $options['widget'] ? self::HTML5_FORMAT : self::DEFAULT_FORMAT; $resolver->setDefaults([ 'years' => range((int) date('Y') - 5, (int) date('Y') + 5), 'months' => range(1, 12), 'days' => range(1, 31), - 'widget' => 'choice', + 'widget' => static function (Options $options) { + trigger_deprecation('symfony/form', '6.3', 'Not configuring the "widget" option of form type "date" is deprecated. It will default to "single_text" in Symfony 7.0.'); + + return 'choice'; + }, 'input' => 'datetime', 'format' => $format, 'model_timezone' => null, @@ -285,9 +290,7 @@ public function configureOptions(OptionsResolver $resolver) // this option. 'data_class' => null, 'compound' => $compound, - 'empty_data' => function (Options $options) { - return $options['compound'] ? [] : ''; - }, + 'empty_data' => static fn (Options $options) => $options['compound'] ? [] : '', 'choice_translation_domain' => false, 'input_format' => 'Y-m-d', 'invalid_message' => 'Please enter a valid date.', @@ -315,7 +318,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('days', 'array'); $resolver->setAllowedTypes('input_format', 'string'); - $resolver->setNormalizer('html5', function (Options $options, $html5) { + $resolver->setNormalizer('html5', static function (Options $options, $html5) { if ($html5 && 'single_text' === $options['widget'] && self::HTML5_FORMAT !== $options['format']) { throw new LogicException(sprintf('Cannot use the "format" option of "%s" when the "html5" option is enabled.', self::class)); } @@ -329,7 +332,7 @@ public function getBlockPrefix(): string return 'date'; } - private function formatTimestamps(\IntlDateFormatter $formatter, string $regex, array $timestamps) + private function formatTimestamps(\IntlDateFormatter $formatter, string $regex, array $timestamps): array { $pattern = $formatter->getPattern(); $timezone = $formatter->getTimeZoneId(); @@ -354,7 +357,7 @@ private function formatTimestamps(\IntlDateFormatter $formatter, string $regex, return $formattedTimestamps; } - private function listYears(array $years) + private function listYears(array $years): array { $result = []; @@ -365,7 +368,7 @@ private function listYears(array $years) return $result; } - private function listMonths(array $months) + private function listMonths(array $months): array { $result = []; @@ -376,7 +379,7 @@ private function listMonths(array $months) return $result; } - private function listDays(array $days) + private function listDays(array $days): array { $result = []; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php b/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php index 70e8c8a191ce3..64d01ee67ac12 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php @@ -16,6 +16,9 @@ class EmailType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/EnumType.php b/src/Symfony/Component/Form/Extension/Core/Type/EnumType.php index 33944c2aec7c4..003819e065738 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/EnumType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/EnumType.php @@ -28,12 +28,8 @@ public function configureOptions(OptionsResolver $resolver): void ->setRequired(['class']) ->setAllowedTypes('class', 'string') ->setAllowedValues('class', enum_exists(...)) - ->setDefault('choices', static function (Options $options): array { - return $options['class']::cases(); - }) - ->setDefault('choice_label', static function (\UnitEnum $choice): string { - return $choice->name; - }) + ->setDefault('choices', static fn (Options $options): array => $options['class']::cases()) + ->setDefault('choice_label', static fn (\UnitEnum $choice): string => $choice->name) ->setDefault('choice_value', static function (Options $options): ?\Closure { if (!is_a($options['class'], \BackedEnum::class, true)) { return null; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 9e8e81394e8ce..cf8e1a7439e57 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -41,6 +41,9 @@ public function __construct(TranslatorInterface $translator = null) $this->translator = $translator; } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { // Ensure that submitted data is always an uploaded file or an array of some @@ -82,6 +85,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) }); } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { if ($options['multiple']) { @@ -95,23 +101,25 @@ public function buildView(FormView $view, FormInterface $form, array $options) ]); } + /** + * @return void + */ public function finishView(FormView $view, FormInterface $form, array $options) { $view->vars['multipart'] = true; } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $dataClass = null; if (class_exists(File::class)) { - $dataClass = function (Options $options) { - return $options['multiple'] ? null : File::class; - }; + $dataClass = static fn (Options $options) => $options['multiple'] ? null : File::class; } - $emptyData = function (Options $options) { - return $options['multiple'] ? [] : null; - }; + $emptyData = static fn (Options $options) => $options['multiple'] ? [] : null; $resolver->setDefaults([ 'compound' => false, @@ -128,7 +136,7 @@ public function getBlockPrefix(): string return 'file'; } - private function getFileUploadError(int $errorCode) + private function getFileUploadError(int $errorCode): FileUploadError { $messageParameters = []; @@ -195,7 +203,7 @@ private static function getMaxFilesize(): int|float * * This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::factorizeSizes(). */ - private function factorizeSizes(int $size, int|float $limit) + private function factorizeSizes(int $size, int|float $limit): array { $coef = self::MIB_BYTES; $coefFactor = self::KIB_BYTES; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index a1f9b6dda8dcf..82aa77f0a3715 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -38,6 +38,9 @@ public function __construct(PropertyAccessorInterface $propertyAccessor = null) ])); } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { parent::buildForm($builder, $options); @@ -66,6 +69,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder->setIsEmptyCallback($options['is_empty_callback']); } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { parent::buildView($view, $form, $options); @@ -105,6 +111,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) ]); } + /** + * @return void + */ public function finishView(FormView $view, FormInterface $form, array $options) { $multipart = false; @@ -119,42 +128,33 @@ public function finishView(FormView $view, FormInterface $form, array $options) $view->vars['multipart'] = $multipart; } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { parent::configureOptions($resolver); // Derive "data_class" option from passed "data" object - $dataClass = function (Options $options) { - return isset($options['data']) && \is_object($options['data']) ? \get_class($options['data']) : null; - }; + $dataClass = static fn (Options $options) => isset($options['data']) && \is_object($options['data']) ? $options['data']::class : null; // Derive "empty_data" closure from "data_class" option - $emptyData = function (Options $options) { + $emptyData = static function (Options $options) { $class = $options['data_class']; if (null !== $class) { - return function (FormInterface $form) use ($class) { - return $form->isEmpty() && !$form->isRequired() ? null : new $class(); - }; + return static fn (FormInterface $form) => $form->isEmpty() && !$form->isRequired() ? null : new $class(); } - return function (FormInterface $form) { - return $form->getConfig()->getCompound() ? [] : ''; - }; + return static fn (FormInterface $form) => $form->getConfig()->getCompound() ? [] : ''; }; // Wrap "post_max_size_message" in a closure to translate it lazily - $uploadMaxSizeMessage = function (Options $options) { - return function () use ($options) { - return $options['post_max_size_message']; - }; - }; + $uploadMaxSizeMessage = static fn (Options $options) => static fn () => $options['post_max_size_message']; // For any form that is not represented by a single HTML control, // errors should bubble up by default - $errorBubbling = function (Options $options) { - return $options['compound'] && !$options['inherit_data']; - }; + $errorBubbling = static fn (Options $options) => $options['compound'] && !$options['inherit_data']; // If data is given, the form is locked to that data // (independent of its value) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php b/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php index a69e172bc6586..c4e5eb2ccfa6b 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/HiddenType.php @@ -16,6 +16,9 @@ class HiddenType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php b/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php index 49e37f7490afd..a287b66b7c9e6 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/IntegerType.php @@ -20,11 +20,17 @@ class IntegerType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addViewTransformer(new IntegerToLocalizedStringTransformer($options['grouping'], $options['rounding_mode'], !$options['grouping'] ? 'en' : null)); } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { if ($options['grouping']) { @@ -32,6 +38,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php index f875657eaaaa9..eeb9e591a12bc 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php @@ -23,6 +23,9 @@ class LanguageType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ @@ -34,7 +37,7 @@ public function configureOptions(OptionsResolver $resolver) $useAlpha3Codes = $options['alpha3']; $choiceSelfTranslation = $options['choice_self_translation']; - return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale, $useAlpha3Codes, $choiceSelfTranslation) { + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static function () use ($choiceTranslationLocale, $useAlpha3Codes, $choiceSelfTranslation) { if (true === $choiceSelfTranslation) { foreach (Languages::getLanguageCodes() as $alpha2Code) { try { @@ -62,7 +65,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']); $resolver->setAllowedTypes('alpha3', 'bool'); - $resolver->setNormalizer('choice_self_translation', function (Options $options, $value) { + $resolver->setNormalizer('choice_self_translation', static function (Options $options, $value) { if (true === $value && $options['choice_translation_locale']) { throw new LogicException('Cannot use the "choice_self_translation" and "choice_translation_locale" options at the same time. Remove one of them.'); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php index d4adca1f5993a..e98134febda87 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php @@ -22,6 +22,9 @@ class LocaleType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ @@ -32,9 +35,7 @@ public function configureOptions(OptionsResolver $resolver) $choiceTranslationLocale = $options['choice_translation_locale']; - return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) { - return array_flip(Locales::getNames($choiceTranslationLocale)); - }), $choiceTranslationLocale); + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => array_flip(Locales::getNames($choiceTranslationLocale))), $choiceTranslationLocale); }, 'choice_translation_domain' => false, 'choice_translation_locale' => null, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php b/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php index e9ecd409eb9f2..9c9e5b4d7c30f 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/MoneyType.php @@ -24,6 +24,9 @@ class MoneyType extends AbstractType { protected static $patterns = []; + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { // Values used in HTML5 number inputs should be formatted as in "1234.5", ie. 'en' format without grouping, @@ -39,6 +42,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) ; } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['money_pattern'] = self::getPattern($options['currency']); @@ -48,6 +54,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ @@ -75,7 +84,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('html5', 'bool'); - $resolver->setNormalizer('grouping', function (Options $options, $value) { + $resolver->setNormalizer('grouping', static function (Options $options, $value) { if ($value && $options['html5']) { throw new LogicException('Cannot use the "grouping" option when the "html5" option is enabled.'); } @@ -94,6 +103,8 @@ public function getBlockPrefix(): string * * The pattern contains the placeholder "{{ widget }}" where the HTML tag should * be inserted + * + * @return string */ protected static function getPattern(?string $currency) { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php b/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php index 614d9b70f1d12..578991f9fd1e9 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php @@ -23,6 +23,9 @@ class NumberType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addViewTransformer(new NumberToLocalizedStringTransformer( @@ -37,6 +40,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { if ($options['html5']) { @@ -50,6 +56,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ @@ -76,7 +85,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('scale', ['null', 'i 10000 nt']); $resolver->setAllowedTypes('html5', 'bool'); - $resolver->setNormalizer('grouping', function (Options $options, $value) { + $resolver->setNormalizer('grouping', static function (Options $options, $value) { if (true === $value && $options['html5']) { throw new LogicException('Cannot use the "grouping" option when the "html5" option is enabled.'); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php b/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php index 495160361d443..0c247f0f3024b 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php @@ -18,6 +18,9 @@ class PasswordType extends AbstractType { + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { if ($options['always_empty'] || !$form->isSubmitted()) { @@ -25,6 +28,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php b/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php index d006c6d5e185a..f71e288b3e5d9 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/PercentType.php @@ -20,6 +20,9 @@ class PercentType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addViewTransformer(new PercentToLocalizedStringTransformer( @@ -30,6 +33,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) )); } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['symbol'] = $options['symbol']; @@ -39,6 +45,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php b/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php index d40f39106d76b..4b97b0ae21f4c 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php @@ -16,6 +16,9 @@ class RadioType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php b/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php index 857028c9c135d..2e33a977d918e 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php @@ -16,6 +16,9 @@ class RangeType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php b/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php index fb55bf2940c01..4176f93e520f2 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php @@ -18,6 +18,9 @@ class RepeatedType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { // Overwrite required option for child fields @@ -41,6 +44,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) ; } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php b/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php index 76ed42011872a..0dca6e42a8bfb 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php @@ -16,6 +16,9 @@ class SearchType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php b/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php index dde44576525a4..3f1b5f95c936a 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php @@ -24,6 +24,9 @@ */ class SubmitType extends AbstractType implements SubmitButtonTypeInterface { + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['clicked'] = $form->isClicked(); @@ -33,6 +36,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefault('validate', true); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TelType.php b/src/Symfony/Component/Form/Extension/Core/Type/TelType.php index 8ba7fd843b353..05fdd41626222 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TelType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TelType.php @@ -16,6 +16,9 @@ class TelType extends AbstractType { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TextType.php b/src/Symfony/Component/Form/Extension/Core/Type/TextType.php index dd019192bc441..479ce054d8d82 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TextType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TextType.php @@ -18,6 +18,9 @@ class TextType extends AbstractType implements DataTransformerInterface { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { // When empty_data is explicitly set to an empty string, @@ -30,6 +33,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php b/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php index 43d8e0eac44b1..40e7580d80b90 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php @@ -17,6 +17,9 @@ class TextareaType extends AbstractType { + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['pattern'] = null; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index 2b8bbb5010214..c7d5276960831 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -34,6 +34,9 @@ class TimeType extends AbstractType 'choice' => ChoiceType::class, ]; + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $parts = ['hour']; @@ -58,7 +61,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) } if ('single_text' === $options['widget']) { - $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $e) use ($options) { + $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $e) use ($options) { $data = $e->getData(); if ($data && preg_match('/^(?P\d{2}):(?P\d{2})(?::(?P\d{2})(?:\.\d+)?)?$/', $data, $matches)) { if ($options['with_seconds']) { @@ -76,7 +79,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) if (null !== $options['reference_date']) { $parseFormat = 'Y-m-d '.$format; - $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) { + $builder->addEventListener(FormEvents::PRE_SUBMIT, static function (FormEvent $event) use ($options) { $data = $event->getData(); if (preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $data)) { @@ -96,12 +99,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) $emptyData = $builder->getEmptyData() ?: []; if ($emptyData instanceof \Closure) { - $lazyEmptyData = static function ($option) use ($emptyData) { - return static function (FormInterface $form) use ($emptyData, $option) { - $emptyData = $emptyData($form->getParent()); + $lazyEmptyData = static fn ($option) => static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); - return $emptyData[$option] ?? ''; - }; + return $emptyData[$option] ?? ''; }; $hourOptions['empty_data'] = $lazyEmptyData('hour'); @@ -209,6 +210,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars = array_replace($view->vars, [ @@ -227,23 +231,26 @@ public function buildView(FormView $view, FormInterface $form, array $options) // adding the HTML attribute step if not already defined. // Otherwise the browser will not display and so not send the seconds // therefore the value will always be considered as invalid. - if ($options['with_seconds'] && !isset($view->vars['attr']['step'])) { - $view->vars['attr']['step'] = 1; + if (!isset($view->vars['attr']['step'])) { + if ($options['with_seconds']) { + $view->vars['attr']['step'] = 1; + } elseif (!$options['with_minutes']) { + $view->vars['attr']['step'] = 3600; + } } } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { - $compound = function (Options $options) { - return 'single_text' !== $options['widget']; - }; + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; - $placeholderDefault = function (Options $options) { - return $options['required'] ? null : ''; - }; + $placeholderDefault = static fn (Options $options) => $options['required'] ? null : ''; - $placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) { + $placeholderNormalizer = static function (Options $options, $placeholder) use ($placeholderDefault) { if (\is_array($placeholder)) { $default = $placeholderDefault($options); @@ -260,7 +267,7 @@ public function configureOptions(OptionsResolver $resolver) ]; }; - $choiceTranslationDomainNormalizer = function (Options $options, $choiceTranslationDomain) { + $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) { if (\is_array($choiceTranslationDomain)) { $default = false; @@ -305,7 +312,11 @@ public function configureOptions(OptionsResolver $resolver) 'hours' => range(0, 23), 'minutes' => range(0, 59), 'seconds' => range(0, 59), - 'widget' => 'choice', + 'widget' => static function (Options $options) { + trigger_deprecation('symfony/form', '6.3', 'Not configuring the "widget" option of form type "time" is deprecated. It will default to "single_text" in Symfony 7.0.'); + + return 'choice'; + }, 'input' => 'datetime', 'input_format' => 'H:i:s', 'with_minutes' => true, @@ -324,15 +335,13 @@ public function configureOptions(OptionsResolver $resolver) // representation is not \DateTime, but an array, we need to unset // this option. 'data_class' => null, - 'empty_data' => function (Options $options) { - return $options['compound'] ? [] : ''; - }, + 'empty_data' => static fn (Options $options) => $options['compound'] ? [] : '', 'compound' => $compound, 'choice_translation_domain' => false, 'invalid_message' => 'Please enter a valid time.', ]); - $resolver->setNormalizer('view_timezone', function (Options $options, $viewTimezone): ?string { + $resolver->setNormalizer('view_timezone', static function (Options $options, $viewTimezone): ?string { if (null !== $options['model_timezone'] && $viewTimezone !== $options['model_timezone'] && null === $options['reference_date']) { throw new LogicException('Using different values for the "model_timezone" and "view_timezone" options without configuring a reference date is not supported.'); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index edea6678e2ef0..a5d4bc61c0bb5 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -25,6 +25,9 @@ class TimezoneType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { if ('datetimezone' === $options['input']) { @@ -34,6 +37,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ @@ -48,14 +54,10 @@ public function configureOptions(OptionsResolver $resolver) $choiceTranslationLocale = $options['choice_translation_locale']; - return ChoiceList::loader($this, new IntlCallbackChoiceLoader(function () use ($input, $choiceTranslationLocale) { - return self::getIntlTimezones($input, $choiceTranslationLocale); - }), [$input, $choiceTranslationLocale]); + return ChoiceList::loader($this, new IntlCallbackChoiceLoader(static fn () => self::getIntlTimezones($input, $choiceTranslationLocale)), [$input, $choiceTranslationLocale]); } - return ChoiceList::lazy($this, function () use ($input) { - return self::getPhpTimezones($input); - }, $input); + return ChoiceList::lazy($this, static fn () => self::getPhpTimezones($input), $input); }, 'choice_translation_domain' => false, 'choice_translation_locale' => null, @@ -67,7 +69,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('intl', ['bool']); $resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']); - $resolver->setNormalizer('choice_translation_locale', function (Options $options, $value) { + $resolver->setNormalizer('choice_translation_locale', static function (Options $options, $value) { if (null !== $value && !$options['intl']) { throw new LogicException('The "choice_translation_locale" option can only be used if the "intl" option is set to true.'); } @@ -76,7 +78,7 @@ public function configureOptions(OptionsResolver $resolver) }); $resolver->setAllowedValues('input', ['string', 'datetimezone', 'intltimezone']); - $resolver->setNormalizer('input', function (Options $options, $value) { + $resolver->setNormalizer('input', static function (Options $options, $value) { if ('intltimezone' === $value && !class_exists(\IntlTimeZone::class)) { throw new LogicException('Cannot use "intltimezone" input because the PHP intl extension is not available.'); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php b/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php index 4fb9cae664bc9..029ad4d43964e 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TransformationFailureExtension.php @@ -28,6 +28,9 @@ public function __construct(TranslatorInterface $translator = null) $this->translator = $translator; } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { if (!isset($options['constraints'])) { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/UlidType.php b/src/Symfony/Component/Form/Extension/Core/Type/UlidType.php index 24eab77d8e77b..ea3da07c02c2d 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/UlidType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/UlidType.php @@ -21,6 +21,9 @@ */ class UlidType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder @@ -28,6 +31,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) ; } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php b/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php index 313fbedbf5334..385c7a25fab55 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php @@ -20,6 +20,9 @@ class UrlType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { if (null !== $options['default_protocol']) { @@ -27,6 +30,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { if ($options['default_protocol']) { @@ -35,6 +41,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/UuidType.php b/src/Symfony/Component/Form/Extension/Core/Type/UuidType.php index 214d308689d34..7c0f65b9a0924 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/UuidType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/UuidType.php @@ -21,6 +21,9 @@ */ class UuidType extends AbstractType { + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder @@ -28,6 +31,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) ; } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php b/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php index 760bb880b1bd0..8027a41a99cd8 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php @@ -28,6 +28,9 @@ class WeekType extends AbstractType 'choice' => ChoiceType::class, ]; + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { if ('string' === $options['input']) { @@ -80,6 +83,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['widget'] = $options['widget']; @@ -89,17 +95,16 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { - $compound = function (Options $options) { - return 'single_text' !== $options['widget']; - }; + $compound = static fn (Options $options) => 'single_text' !== $options['widget']; - $placeholderDefault = function (Options $options) { - return $options['required'] ? null : ''; - }; + $placeholderDefault = static fn (Options $options) => $options['required'] ? null : ''; - $placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) { + $placeholderNormalizer = static function (Options $options, $placeholder) use ($placeholderDefault) { if (\is_array($placeholder)) { $default = $placeholderDefault($options); @@ -115,7 +120,7 @@ public function configureOptions(OptionsResolver $resolver) ]; }; - $choiceTranslationDomainNormalizer = function (Options $options, $choiceTranslationDomain) { + $choiceTranslationDomainNormalizer = static function (Options $options, $choiceTranslationDomain) { if (\is_array($choiceTranslationDomain)) { $default = false; @@ -137,13 +142,9 @@ public function configureOptions(OptionsResolver $resolver) 'widget' => 'single_text', 'input' => 'array', 'placeholder' => $placeholderDefault, - 'html5' => static function (Options $options) { - return 'single_text' === $options['widget']; - }, + 'html5' => static fn (Options $options) => 'single_text' === $options['widget'], 'error_bubbling' => false, - 'empty_data' => function (Options $options) { - return $options['compound'] ? [] : ''; - }, + 'empty_data' => static fn (Options $options) => $options['compound'] ? [] : '', 'compound' => $compound, 'choice_translation_domain' => false, 'invalid_message' => 'Please enter a valid week.', @@ -151,7 +152,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setNormalizer('placeholder', $placeholderNormalizer); $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); - $resolver->setNormalizer('html5', function (Options $options, $html5) { + $resolver->setNormalizer('html5', static function (Options $options, $html5) { if ($html5 && 'single_text' !== $options['widget']) { throw new LogicException(sprintf('The "widget" option of "%s" must be set to "single_text" when the "html5" option is enabled.', self::class)); } diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php index 732b036de1fbb..eca450a165d42 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -51,6 +51,9 @@ public function __construct(string $fieldName, CsrfTokenManagerInterface $tokenM $this->serverParams = $serverParams ?? new ServerParams(); } + /** + * @return void + */ public function preSubmit(FormEvent $event) { $form = $event->getForm(); diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index ec96dc6aedfb7..8c3d45dec0744 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -47,6 +47,8 @@ public function __construct(CsrfTokenManagerInterface $defaultTokenManager, bool /** * Adds a CSRF field to the form when the CSRF protection is enabled. + * + * @return void */ public function buildForm(FormBuilderInterface $builder, array $options) { @@ -58,7 +60,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) ->addEventSubscriber(new CsrfValidationListener( $options['csrf_field_name'], $options['csrf_token_manager'], - $options['csrf_token_id'] ?: ($builder->getName() ?: \get_class($builder->getType()->getInnerType())), + $options['csrf_token_id'] ?: ($builder->getName() ?: $builder->getType()->getInnerType()::class), $options['csrf_message'], $this->translator, $this->translationDomain, @@ -69,12 +71,14 @@ public function buildForm(FormBuilderInterface $builder, array $options) /** * Adds a CSRF field to the root form view. + * + * @return void */ public function finishView(FormView $view, FormInterface $form, array $options) { if ($options['csrf_protection'] && !$view->parent && $options['compound']) { $factory = $form->getConfig()->getFormFactory(); - $tokenId = $options['csrf_token_id'] ?: ($form->getName() ?: \get_class($form->getConfig()->getType()->getInnerType())); + $tokenId = $options['csrf_token_id'] ?: ($form->getName() ?: $form->getConfig()->getType()->getInnerType()::class); $data = (string) $options['csrf_token_manager']->getToken($tokenId); $csrfForm = $factory->createNamed($options['csrf_field_name'], HiddenType::class, $data, [ @@ -86,6 +90,9 @@ public function finishView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php b/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php index be30912c7cc6b..41a52e091edc6 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/EventListener/DataCollectorListener.php @@ -43,6 +43,8 @@ public static function getSubscribedEvents(): array /** * Listener for the {@link FormEvents::POST_SET_DATA} event. + * + * @return void */ public function postSetData(FormEvent $event) { @@ -57,6 +59,8 @@ public function postSetData(FormEvent $event) /** * Listener for the {@link FormEvents::POST_SUBMIT} event. + * + * @return void */ public function postSubmit(FormEvent $event) { diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php index 0a9145c624e68..ffce3ac137f56 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php @@ -76,11 +76,11 @@ public function __construct(FormDataExtractorInterface $dataExtractor) /** * Does nothing. The data is collected during the form event listeners. */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { } - public function reset() + public function reset(): void { $this->data = [ 'forms' => [], @@ -89,12 +89,12 @@ public function reset() ]; } - public function associateFormWithView(FormInterface $form, FormView $view) + public function associateFormWithView(FormInterface $form, FormView $view): void { $this->formsByView[spl_object_hash($view)] = spl_object_hash($form); } - public function collectConfiguration(FormInterface $form) + public function collectConfiguration(FormInterface $form): void { $hash = spl_object_hash($form); @@ -112,7 +112,7 @@ public function collectConfiguration(FormInterface $form) } } - public function collectDefaultData(FormInterface $form) + public function collectDefaultData(FormInterface $form): void { $hash = spl_object_hash($form); @@ -131,7 +131,7 @@ public function collectDefaultData(FormInterface $form) } } - public function collectSubmittedData(FormInterface $form) + public function collectSubmittedData(FormInterface $form): void { $hash = spl_object_hash($form); @@ -162,7 +162,7 @@ public function collectSubmittedData(FormInterface $form) } } - public function collectViewVariables(FormView $view) + public function collectViewVariables(FormView $view): void { $hash = spl_object_hash($view); @@ -180,12 +180,12 @@ public function collectViewVariables(FormView $view) } } - public function buildPreliminaryFormTree(FormInterface $form) + public function buildPreliminaryFormTree(FormInterface $form): void { $this->data['forms'][$form->getName()] = &$this->recursiveBuildPreliminaryFormTree($form, $this->data['forms_by_hash']); } - public function buildFinalFormTree(FormInterface $form, FormView $view) + public function buildFinalFormTree(FormInterface $form, FormView $view): void { $this->data['forms'][$form->getName()] = &$this->recursiveBuildFinalFormTree($form, $view, $this->data['forms_by_hash']); } @@ -219,7 +219,7 @@ public function __sleep(): array protected function getCasters(): array { return parent::getCasters() + [ - \Exception::class => function (\Exception $e, array $a, Stub $s) { + \Exception::class => static function (\Exception $e, array $a, Stub $s) { foreach (["\0Exception\0previous", "\0Exception\0trace"] as $k) { if (isset($a[$k])) { unset($a[$k]); @@ -229,20 +229,16 @@ protected function getCasters(): array return $a; }, - FormInterface::class => function (FormInterface $f, array $a) { - return [ - Caster::PREFIX_VIRTUAL.'name' => $f->getName(), - Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub(\get_class($f->getConfig()->getType()->getInnerType())), - ]; - }, + FormInterface::class => static fn (FormInterface $f, array $a) => [ + Caster::PREFIX_VIRTUAL.'name' => $f->getName(), + Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub($f->getConfig()->getType()->getInnerType()::class), + ], FormView::class => StubCaster::cutInternals(...), - ConstraintViolationInterface::class => function (ConstraintViolationInterface $v, array $a) { - return [ - Caster::PREFIX_VIRTUAL.'root' => $v->getRoot(), - Caster::PREFIX_VIRTUAL.'path' => $v->getPropertyPath(), - Caster::PREFIX_VIRTUAL.'value' => $v->getInvalidValue(), - ]; - }, + ConstraintViolationInterface::class => static fn (ConstraintViolationInterface $v, array $a) => [ + Caster::PREFIX_VIRTUAL.'root' => $v->getRoot(), + Caster::PREFIX_VIRTUAL.'path' => $v->getPropertyPath(), + Caster::PREFIX_VIRTUAL.'value' => $v->getInvalidValue(), + ], ]; } diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php index 0e1d32bfc857c..346c101fe32a8 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollectorInterface.php @@ -25,26 +25,36 @@ interface FormDataCollectorInterface extends DataCollectorInterface { /** * Stores configuration data of the given form and its children. + * + * @return void */ public function collectConfiguration(FormInterface $form); /** * Stores the default data of the given form and its children. + * + * @return void */ public function collectDefaultData(FormInterface $form); /** * Stores the submitted data of the given form and its children. + * + * @return void */ public function collectSubmittedData(FormInterface $form); /** * Stores the view variables of the given form view and its children. + * + * @return void */ public function collectViewVariables(FormView $view); /** * Specifies that the given objects represent the same conceptual form. + * + * @return void */ public function associateFormWithView(FormInterface $form, FormView $view); @@ -53,6 +63,8 @@ public function associateFormWithView(FormInterface $form, FormView $view); * a tree-like data structure. * * The result can be queried using {@link getData()}. + * + * @return void */ public function buildPreliminaryFormTree(FormInterface $form); @@ -73,6 +85,8 @@ public function buildPreliminaryFormTree(FormInterface $form); * tree, only the view data will be included in the result. If a * corresponding {@link FormInterface} exists otherwise, call * {@link associateFormWithView()} before calling this method. + * + * @return void */ public function buildFinalFormTree(FormInterface $form, FormView $view); diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php index 028e8c06d033b..158cf321092b3 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php @@ -27,7 +27,7 @@ public function extractConfiguration(FormInterface $form): array $data = [ 'id' => $this->buildId($form), 'name' => $form->getName(), - 'type_class' => \get_class($form->getConfig()->getType()->getInnerType()), + 'type_class' => $form->getConfig()->getType()->getInnerType()::class, 'synchronized' => $form->isSynchronized(), 'passed_options' => [], 'resolved_options' => [], diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php index d273fbbc757ad..6c8cf3ee24614 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php @@ -71,16 +71,25 @@ public function createView(FormInterface $form, FormView $parent = null): FormVi return $this->proxiedType->createView($form, $parent); } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $this->proxiedType->buildForm($builder, $options); } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $this->proxiedType->buildView($view, $form, $options); } + /** + * @return void + */ public function finishView(FormView $view, FormInterface $form, array $options) { $this->proxiedType->finishView($view, $form, $options); diff --git a/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php b/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php index d014f0e457f72..f1e3c903ec4c5 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/Type/DataCollectorTypeExtension.php @@ -32,6 +32,9 @@ public function __construct(FormDataCollectorInterface $dataCollector) $this->listener = new DataCollectorListener($dataCollector); } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addEventSubscriber($this->listener); diff --git a/src/Symfony/Component/Form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php b/src/Symfony/Component/Form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php index 4b2dffe9a13af..8e92ea74a5ea5 100644 --- a/src/Symfony/Component/Form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php +++ b/src/Symfony/Component/Form/Extension/HtmlSanitizer/Type/TextTypeHtmlSanitizerExtension.php @@ -35,6 +35,9 @@ public static function getExtendedTypes(): iterable return [TextType::class]; } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver @@ -44,6 +47,9 @@ public function configureOptions(OptionsResolver $resolver) ; } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { if (!$options['sanitize_html']) { diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php index 3b1aaebf02648..b4e835c95ae02 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -35,6 +35,9 @@ public function __construct(ServerParams $serverParams = null) $this->serverParams = $serverParams ?? new ServerParams(); } + /** + * @return void + */ public function handleRequest(FormInterface $form, mixed $request = null) { if (!$request instanceof Request) { diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php b/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php index 9a5ae66618ddc..cc3e5e1207715 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php @@ -29,6 +29,9 @@ public function __construct(RequestHandlerInterface $requestHandler = null) $this->requestHandler = $requestHandler ?? new HttpFoundationRequestHandler(); } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->setRequestHandler($this->requestHandler); diff --git a/src/Symfony/Component/Form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php b/src/Symfony/Component/Form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php index bb74e7792e2f5..4854dd3e73601 100644 --- a/src/Symfony/Component/Form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php +++ b/src/Symfony/Component/Form/Extension/PasswordHasher/EventListener/PasswordHasherListener.php @@ -35,6 +35,9 @@ public function __construct( $this->propertyAccessor ??= PropertyAccess::createPropertyAccessor(); } + /** + * @return void + */ public function registerPassword(FormEvent $event) { if (null === $event->getData() || '' === $event->getData()) { @@ -50,6 +53,9 @@ public function registerPassword(FormEvent $event) ]; } + /** + * @return void + */ public function hashPasswords(FormEvent $event) { $form = $event->getForm(); diff --git a/src/Symfony/Component/Form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php b/src/Symfony/Component/Form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php index 7294ee5f022d9..5308992863f5d 100644 --- a/src/Symfony/Component/Form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php +++ b/src/Symfony/Component/Form/Extension/PasswordHasher/Type/FormTypePasswordHasherExtension.php @@ -27,6 +27,9 @@ public function __construct( ) { } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addEventListener(FormEvents::POST_SUBMIT, [$this->passwordHasherListener, 'hashPasswords']); diff --git a/src/Symfony/Component/Form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php b/src/Symfony/Component/Form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php index e75a6e9ff2579..6f022fb1bf619 100644 --- a/src/Symfony/Component/Form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php +++ b/src/Symfony/Component/Form/Extension/PasswordHasher/Type/PasswordTypePasswordHasherExtension.php @@ -29,6 +29,9 @@ public function __construct( ) { } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { if ($options['hash_property_path']) { @@ -36,6 +39,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index ada84f14bf89b..d664e9b50008f 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -29,6 +29,9 @@ class FormValidator extends ConstraintValidator */ private \SplObjectStorage $resolvedGroups; + /** + * @return void + */ public function validate(mixed $form, Constraint $formConstraint) { if (!$formConstraint instanceof Form) { diff --git a/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php b/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php index 2963d6f7b02ef..e2d4357622bd6 100644 --- a/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php @@ -37,6 +37,9 @@ public function __construct(ValidatorInterface $validator, ViolationMapperInterf $this->violationMapper = $violationMapper; } + /** + * @return void + */ public function validateForm(FormEvent $event) { $form = $event->getForm(); diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php index 9366a20f89ea6..ea01d03699cfd 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php @@ -24,10 +24,13 @@ */ abstract class BaseValidatorExtension extends AbstractTypeExtension { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { // Make sure that validation groups end up as null, closure or array - $validationGroupsNormalizer = function (Options $options, $groups) { + $validationGroupsNormalizer = static function (Options $options, $groups) { if (false === $groups) { return []; } diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php index 26653dc9985b0..54eebaf63e43b 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -37,19 +37,23 @@ public function __construct(ValidatorInterface $validator, bool $legacyErrorMess $this->violationMapper = new ViolationMapper($formRenderer, $translator); } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addEventSubscriber(new ValidationListener($this->validator, $this->violationMapper)); } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { parent::configureOptions($resolver); // Constraint should always be converted to an array - $constraintsNormalizer = function (Options $options, $constraints) { - return \is_object($constraints) ? [$constraints] : (array) $constraints; - }; + $constraintsNormalizer = static fn (Options $options, $constraints) => \is_object($constraints) ? [$constraints] : (array) $constraints; $resolver->setDefaults([ 'error_mapping' => [], diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php index 664a3edae2766..d41dc0168c311 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php @@ -21,12 +21,13 @@ */ class RepeatedTypeValidatorExtension extends AbstractTypeExtension { + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { // Map errors to the first field - $errorMapping = function (Options $options) { - return ['.' => $options['first_name']]; - }; + $errorMapping = static fn (Options $options) => ['.' => $options['first_name']]; $resolver->setDefaults([ 'error_mapping' => $errorMapping, diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php index 14f6c8f2d8b7e..b7a19ed26a490 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php @@ -32,15 +32,14 @@ public function __construct(TranslatorInterface $translator, string $translation $this->translationDomain = $translationDomain; } + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver) { $translator = $this->translator; $translationDomain = $this->translationDomain; - $resolver->setNormalizer('upload_max_size_message', function (Options $options, $message) use ($translator, $translationDomain) { - return function () use ($translator, $translationDomain, $message) { - return $translator->trans($message(), [], $translationDomain); - }; - }); + $resolver->setNormalizer('upload_max_size_message', static fn (Options $options, $message) => static fn () => $translator->trans($message(), [], $translationDomain)); } public static function getExtendedTypes(): iterable diff --git a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php index 93a0974852414..9a74a125ed00a 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php +++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php @@ -66,32 +66,24 @@ public function __construct(MetadataFactoryInterface $metadataFactory) public function guessType(string $class, string $property): ?TypeGuess { - return $this->guess($class, $property, function (Constraint $constraint) { - return $this->guessTypeForConstraint($constraint); - }); + return $this->guess($class, $property, $this->guessTypeForConstraint(...)); } public function guessRequired(string $class, string $property): ?ValueGuess { - return $this->guess($class, $property, function (Constraint $constraint) { - return $this->guessRequiredForConstraint($constraint); - // If we don't find any constraint telling otherwise, we can assume - // that a field is not required (with LOW_CONFIDENCE) - }, false); + // If we don't find any constraint telling otherwise, we can assume + // that a field is not required (with LOW_CONFIDENCE) + return $this->guess($class, $property, $this->guessRequiredForConstraint(...), false); } public function guessMaxLength(string $class, string $property): ?ValueGuess { - return $this->guess($class, $property, function (Constraint $constraint) { - return $this->guessMaxLengthForConstraint($constraint); - }); + return $this->guess($class, $property, $this->guessMaxLengthForConstraint(...)); } public function guessPattern(string $class, string $property): ?ValueGuess { - return $this->guess($class, $property, function (Constraint $constraint) { - return $this->guessPatternForConstraint($constraint); - }); + return $this->guess($class, $property, $this->guessPatternForConstraint(...)); } /** diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php index e6bcd29fea991..2f2ccefd30b99 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php @@ -38,6 +38,9 @@ public function __construct(FormRendererInterface $formRenderer = null, Translat $this->translator = $translator; } + /** + * @return void + */ public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false) { $this->allowNonSynchronized = $allowNonSynchronized; diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php index 57ed1c84ce909..a72d41df9ec10 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php @@ -24,6 +24,8 @@ interface ViolationMapperInterface * the given form. * * @param bool $allowNonSynchronized Whether to allow mapping to non-synchronized forms + * + * @return void */ public function mapViolation(ConstraintViolation $violation, FormInterface $form, bool $allowNonSynchronized = false); } diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php index a597dcb1391e7..a9a0f15d6e242 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php @@ -193,7 +193,7 @@ public function getIterator(): ViolationPathIterator /** * Builds the string representation from the elements. */ - private function buildString() + private function buildString(): void { $this->pathAsString = ''; $data = false; diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php index 50baa4533e318..ed363a7b158f3 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php @@ -23,6 +23,9 @@ public function __construct(ViolationPath $violationPath) parent::__construct($violationPath); } + /** + * @return bool + */ public function mapsForm() { return $this->path->mapsForm($this->key()); diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 7acf8c7251a3c..a4b76506a2f91 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -636,17 +636,17 @@ public function isEmpty(): bool return $isEmptyCallback($this->modelData); } - return FormUtil::isEmpty($this->modelData) || + return FormUtil::isEmpty($this->modelData) // arrays, countables - (is_countable($this->modelData) && 0 === \count($this->modelData)) || + || (is_countable($this->modelData) && 0 === \count($this->modelData)) // traversables that are not countable - ($this->modelData instanceof \Traversable && 0 === iterator_count($this->modelData)); + || ($this->modelData instanceof \Traversable && 0 === iterator_count($this->modelData)); } public function isValid(): bool { if (!$this->submitted) { - throw new LogicException('Cannot check if an unsubmitted form is valid. Call Form::isSubmitted() before Form::isValid().'); + throw new LogicException('Cannot check if an unsubmitted form is valid. Call Form::isSubmitted() and ensure that it\'s true before calling Form::isValid().'); } if ($this->isDisabled()) { @@ -930,9 +930,7 @@ private function sort(array &$children): void return; } - uksort($children, static function ($a, $b) use ($c): int { - return [$c[$b]['p'], $c[$a]['i']] <=> [$c[$a]['p'], $c[$b]['i']]; - }); + uksort($children, static fn ($a, $b): int => [$c[$b]['p'], $c[$a]['i']] <=> [$c[$a]['p'], $c[$b]['i']]); } /** diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index c6215635eccda..33f07b0f1dc05 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -206,7 +206,7 @@ private function resolveChild(string $name): FormBuilderInterface /** * Converts all unresolved children into {@link FormBuilder} instances. */ - private function resolveChildren() + private function resolveChildren(): void { foreach ($this->unresolvedChildren as $name => $info) { $this->children[$name] = $this->create($name, $info[0], $info[1]); diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index e18db7ed8e320..9fed3d1a0a5f4 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -316,6 +316,9 @@ public function getIsEmptyCallback(): ?callable return $this->isEmptyCallback; } + /** + * @return $this + */ public function setAttribute(string $name, mixed $value): static { if ($this->locked) { @@ -327,6 +330,9 @@ public function setAttribute(string $name, mixed $value): static return $this; } + /** + * @return $this + */ public function setAttributes(array $attributes): static { if ($this->locked) { @@ -338,6 +344,9 @@ public function setAttributes(array $attributes): static return $this; } + /** + * @return $this + */ public function setDataMapper(DataMapperInterface $dataMapper = null): static { if (1 > \func_num_args()) { @@ -352,6 +361,9 @@ public function setDataMapper(DataMapperInterface $dataMapper = null): static return $this; } + /** + * @return $this + */ public function setDisabled(bool $disabled): static { if ($this->locked) { @@ -363,6 +375,9 @@ public function setDisabled(bool $disabled): static return $this; } + /** + * @return $this + */ public function setEmptyData(mixed $emptyData): static { if ($this->locked) { @@ -374,6 +389,9 @@ public function setEmptyData(mixed $emptyData): static return $this; } + /** + * @return $this + */ public function setErrorBubbling(bool $errorBubbling): static { if ($this->locked) { @@ -385,6 +403,9 @@ public function setErrorBubbling(bool $errorBubbling): static return $this; } + /** + * @return $this + */ public function setRequired(bool $required): static { if ($this->locked) { @@ -396,6 +417,9 @@ public function setRequired(bool $required): static return $this; } + /** + * @return $this + */ public function setPropertyPath(string|PropertyPathInterface|null $propertyPath): static { if ($this->locked) { @@ -411,6 +435,9 @@ public function setPropertyPath(string|PropertyPathInterface|null $propertyPath) return $this; } + /** + * @return $this + */ public function setMapped(bool $mapped): static { if ($this->locked) { @@ -422,6 +449,9 @@ public function setMapped(bool $mapped): static return $this; } + /** + * @return $this + */ public function setByReference(bool $byReference): static { if ($this->locked) { @@ -433,6 +463,9 @@ public function setByReference(bool $byReference): static return $this; } + /** + * @return $this + */ public function setInheritData(bool $inheritData): static { if ($this->locked) { @@ -444,6 +477,9 @@ public function setInheritData(bool $inheritData): static return $this; } + /** + * @return $this + */ public function setCompound(bool $compound): static { if ($this->locked) { @@ -455,6 +491,9 @@ public function setCompound(bool $compound): static return $this; } + /** + * @return $this + */ public function setType(ResolvedFormTypeInterface $type): static { if ($this->locked) { @@ -466,6 +505,9 @@ public function setType(ResolvedFormTypeInterface $type): static return $this; } + /** + * @return $this + */ public function setData(mixed $data): static { if ($this->locked) { @@ -477,6 +519,9 @@ public function setData(mixed $data): static return $this; } + /** + * @return $this + */ public function setDataLocked(bool $locked): static { if ($this->locked) { @@ -488,6 +533,9 @@ public function setDataLocked(bool $locked): static return $this; } + /** + * @return $this + */ public function setFormFactory(FormFactoryInterface $formFactory) { if ($this->locked) { @@ -499,6 +547,9 @@ public function setFormFactory(FormFactoryInterface $formFactory) return $this; } + /** + * @return $this + */ public function setAction(string $action): static { if ($this->locked) { @@ -510,6 +561,9 @@ public function setAction(string $action): static return $this; } + /** + * @return $this + */ public function setMethod(string $method): static { if ($this->locked) { @@ -521,6 +575,9 @@ public function setMethod(string $method): static return $this; } + /** + * @return $this + */ public function setRequestHandler(RequestHandlerInterface $requestHandler): static { if ($this->locked) { @@ -532,6 +589,9 @@ public function setRequestHandler(RequestHandlerInterface $requestHandler): stat return $this; } + /** + * @return $this + */ public function setAutoInitialize(bool $initialize): static { if ($this->locked) { @@ -556,6 +616,9 @@ public function getFormConfig(): FormConfigInterface return $config; } + /** + * @return $this + */ public function setIsEmptyCallback(?callable $isEmptyCallback): static { $this->isEmptyCallback = null === $isEmptyCallback ? null : $isEmptyCallback(...); @@ -570,7 +633,7 @@ public function setIsEmptyCallback(?callable $isEmptyCallback): static * * @internal */ - final public static function validateName(?string $name) + final public static function validateName(?string $name): void { if (!self::isValidName($name)) { throw new InvalidArgumentException(sprintf('The name "%s" contains illegal characters. Names should start with a letter, digit or underscore and only contain letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").', $name)); diff --git a/src/Symfony/Component/Form/FormConfigBuilderInterface.php b/src/Symfony/Component/Form/FormConfigBuilderInterface.php index 84f40d3711f6b..09b91498013a6 100644 --- a/src/Symfony/Component/Form/FormConfigBuilderInterface.php +++ b/src/Symfony/Component/Form/FormConfigBuilderInterface.php @@ -205,6 +205,8 @@ public function setDataLocked(bool $locked): static; /** * Sets the form factory used for creating new forms. + * + * @return $this */ public function setFormFactory(FormFactoryInterface $formFactory); diff --git a/src/Symfony/Component/Form/FormError.php b/src/Symfony/Component/Form/FormError.php index aafd723f22673..572783c7ac4d7 100644 --- a/src/Symfony/Component/Form/FormError.php +++ b/src/Symfony/Component/Form/FormError.php @@ -99,6 +99,8 @@ public function getCause(): mixed * * This method must only be called once. * + * @return void + * * @throws BadMethodCallException If the method is called more than once */ public function setOrigin(FormInterface $origin) diff --git a/src/Symfony/Component/Form/FormEvent.php b/src/Symfony/Component/Form/FormEvent.php index c9c3053cfe9a9..1e6aa34d63ad6 100644 --- a/src/Symfony/Component/Form/FormEvent.php +++ b/src/Symfony/Component/Form/FormEvent.php @@ -45,6 +45,8 @@ public function getData(): mixed /** * Allows updating with some filtered data. + * + * @return void */ public function setData(mixed $data) { diff --git a/src/Symfony/Component/Form/FormRenderer.php b/src/Symfony/Component/Form/FormRenderer.php index 12c7c0dddd5cd..18dec4946b83e 100644 --- a/src/Symfony/Component/Form/FormRenderer.php +++ b/src/Symfony/Component/Form/FormRenderer.php @@ -42,6 +42,9 @@ public function getEngine(): FormRendererEngineInterface return $this->engine; } + /** + * @return void + */ public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true) { $this->engine->setTheme($view, $themes, $useDefaultThemes); diff --git a/src/Symfony/C 10000 omponent/Form/FormRendererEngineInterface.php b/src/Symfony/Component/Form/FormRendererEngineInterface.php index aa249270a0688..e7de3544a17c4 100644 --- a/src/Symfony/Component/Form/FormRendererEngineInterface.php +++ b/src/Symfony/Component/Form/FormRendererEngineInterface.php @@ -24,6 +24,8 @@ interface FormRendererEngineInterface * @param FormView $view The view to assign the theme(s) to * @param mixed $themes The theme(s). The type of these themes * is open to the implementation. + * + * @return void */ public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true); diff --git a/src/Symfony/Component/Form/FormRendererInterface.php b/src/Symfony/Component/Form/FormRendererInterface.php index 240cd14113e35..8e805727cea68 100644 --- a/src/Symfony/Component/Form/FormRendererInterface.php +++ b/src/Symfony/Component/Form/FormRendererInterface.php @@ -31,6 +31,8 @@ public function getEngine(): FormRendererEngineInterface; * is open to the implementation. * @param bool $useDefaultThemes If true, will use default themes specified * in the renderer + * + * @return void */ public function setTheme(FormView $view, mixed $themes, bool $useDefaultThemes = true); diff --git a/src/Symfony/Component/Form/FormTypeExtensionInterface.php b/src/Symfony/Component/Form/FormTypeExtensionInterface.php index 3c7b46ce9c7f2..1937834515ceb 100644 --- a/src/Symfony/Component/Form/FormTypeExtensionInterface.php +++ b/src/Symfony/Component/Form/FormTypeExtensionInterface.php @@ -26,6 +26,8 @@ interface FormTypeExtensionInterface * * @param array $options * + * @return void + * * @see FormTypeInterface::buildForm() */ public function buildForm(FormBuilderInterface $builder, array $options); @@ -38,6 +40,8 @@ public function buildForm(FormBuilderInterface $builder, array $options); * * @param array $options * + * @return void + * * @see FormTypeInterface::buildView() */ public function buildView(FormView $view, FormInterface $form, array $options); @@ -50,10 +54,15 @@ public function buildView(FormView $view, FormInterface $form, array $options); * * @param array $options * + * @return void + * * @see FormTypeInterface::finishView() */ public function finishView(FormView $view, FormInterface $form, array $options); + /** + * @return void + */ public function configureOptions(OptionsResolver $resolver); /** diff --git a/src/Symfony/Component/Form/FormTypeGuesserChain.php b/src/Symfony/Component/Form/FormTypeGuesserChain.php index 7d8f617f7baaa..ed94ece6e9d0a 100644 --- a/src/Symfony/Component/Form/FormTypeGuesserChain.php +++ b/src/Symfony/Component/Form/FormTypeGuesserChain.php @@ -45,30 +45,22 @@ public function __construct(iterable $guessers) public function guessType(string $class, string $property): ?TypeGuess { - return $this->guess(function ($guesser) use ($class, $property) { - return $guesser->guessType($class, $property); - }); + return $this->guess(static fn ($guesser) => $guesser->guessType($class, $property)); } public function guessRequired(string $class, string $property): ?ValueGuess { - return $this->guess(function ($guesser) use ($class, $property) { - return $guesser->guessRequired($class, $property); - }); + return $this->guess(static fn ($guesser) => $guesser->guessRequired($class, $property)); } public function guessMaxLength(string $class, string $property): ?ValueGuess { - return $this->guess(function ($guesser) use ($class, $property) { - return $guesser->guessMaxLength($class, $property); - }); + return $this->guess(static fn ($guesser) => $guesser->guessMaxLength($class, $property)); } public function guessPattern(string $class, string $property): ?ValueGuess { - return $this->guess(function ($guesser) use ($class, $property) { - return $guesser->guessPattern($class, $property); - }); + return $this->guess(static fn ($guesser) => $guesser->guessPattern($class, $property)); } /** diff --git a/src/Symfony/Component/Form/FormTypeInterface.php b/src/Symfony/Component/Form/FormTypeInterface.php index 2b9066a511f42..0c586d3f71b91 100644 --- a/src/Symfony/Component/Form/FormTypeInterface.php +++ b/src/Symfony/Component/Form/FormTypeInterface.php @@ -26,6 +26,8 @@ interface FormTypeInterface * * @param array $options * + * @return void + * * @see FormTypeExtensionInterface::buildForm() */ public function buildForm(FormBuilderInterface $builder, array $options); @@ -42,6 +44,8 @@ public function buildForm(FormBuilderInterface $builder, array $options); * * @param array $options * + * @return void + * * @see FormTypeExtensionInterface::buildView() */ public function buildView(FormView $view, FormInterface $form, array $options); @@ -59,12 +63,16 @@ public function buildView(FormView $view, FormInterface $form, array $options); * * @param array $options * + * @return void + * * @see FormTypeExtensionInterface::finishView() */ public function finishView(FormView $view, FormInterface $form, array $options); /** * Configures the options for this type. + * + * @return void */ public function configureOptions(OptionsResolver $resolver); diff --git a/src/Symfony/Component/Form/FormView.php b/src/Symfony/Component/Form/FormView.php index fe32f90ceff61..e04fa13b09896 100644 --- a/src/Symfony/Component/Form/FormView.php +++ b/src/Symfony/Component/Form/FormView.php @@ -92,6 +92,9 @@ public function isMethodRendered(): bool return $this->methodRendered; } + /** + * @return void + */ public function setMethodRendered() { $this->methodRendered = true; diff --git a/src/Symfony/Component/Form/Guess/Guess.php b/src/Symfony/Component/Form/Guess/Guess.php index abe09cb39fd8a..fc19ed9ceee68 100644 --- a/src/Symfony/Component/Form/Guess/Guess.php +++ b/src/Symfony/Component/Form/Guess/Guess.php @@ -80,8 +80,8 @@ public static function getBestGuess(array $guesses): ?static */ public function __construct(int $confidence) { - if (self::VERY_HIGH_CONFIDENCE !== $confidence && self::HIGH_CONFIDENCE !== $confidence && - self::MEDIUM_CONFIDENCE !== $confidence && self::LOW_CONFIDENCE !== $confidence) { + if (self::VERY_HIGH_CONFIDENCE !== $confidence && self::HIGH_CONFIDENCE !== $confidence + && self::MEDIUM_CONFIDENCE !== $confidence && self::LOW_CONFIDENCE !== $confidence) { throw new InvalidArgumentException('The confidence should be one of the constants defined in Guess.'); } diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index cf193398c8318..11c4d4d9c07c8 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -40,6 +40,8 @@ public function __construct(ServerParams $params = null) } /** + * @return void + * * @throws Exception\UnexpectedTypeException If the $request is not null */ public function handleRequest(FormInterface $form, mixed $request = null) diff --git a/src/Symfony/Component/Form/RequestHandlerInterface.php b/src/Symfony/Component/Form/RequestHandlerInterface.php index 8eb8f1a688ac5..39fd458ee40ca 100644 --- a/src/Symfony/Component/Form/RequestHandlerInterface.php +++ b/src/Symfony/Component/Form/RequestHandlerInterface.php @@ -20,6 +20,8 @@ interface RequestHandlerInterface { /** * Submits a form if it was submitted. + * + * @return void */ public function handleRequest(FormInterface $form, mixed $request = null); diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php index af794a3b7c0d6..f05db1533b71c 100644 --- a/src/Symfony/Component/Form/ResolvedFormType.php +++ b/src/Symfony/Component/Form/ResolvedFormType.php @@ -92,6 +92,9 @@ public function createView(FormInterface $form, FormView $parent = null): FormVi return $this->newView($parent); } + /** + * @return void + */ public function buildForm(FormBuilderInterface $builder, array $options) { $this->parent?->buildForm($builder, $options); @@ -103,6 +106,9 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } + /** + * @return void + */ public function buildView(FormView $view, FormInterface $form, array $options) { $this->parent?->buildView($view, $form, $options); @@ -114,6 +120,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } } + /** + * @return void + */ public function finishView(FormView $view, FormInterface $form, array $options) { $this->parent?->finishView($view, $form, $options); diff --git a/src/Symfony/Component/Form/ResolvedFormTypeInterface.php b/src/Symfony/Component/Form/ResolvedFormTypeInterface.php index f101177701eba..e0b96a5ac36d1 100644 --- a/src/Symfony/Component/Form/ResolvedFormTypeInterface.php +++ b/src/Symfony/Component/Form/ResolvedFormTypeInterface.php @@ -56,6 +56,8 @@ public function createView(FormInterface $form, FormView $parent = null): FormVi /** * Configures a form builder for the type hierarchy. + * + * @return void */ public function buildForm(FormBuilderInterface $builder, array $options); @@ -63,6 +65,8 @@ public function buildForm(FormBuilderInterface $builder, array $options); * Configures a form view for the type hierarchy. * * It is called before the children of the view are built. + * + * @return void */ public function buildView(FormView $view, FormInterface $form, array $options); @@ -70,6 +74,8 @@ public function buildView(FormView $view, FormInterface $form, array $options); * Finishes a form view for the type hierarchy. * * It is called after the children of the view have been built. + * + * @return void */ public function finishView(FormView $view, FormInterface $form, array $options); diff --git a/src/Symfony/Component/Form/Tests/CallbackTransformerTest.php b/src/Symfony/Component/Form/Tests/CallbackTransformerTest.php index 9142e1fa3c5e3..1fb6134dd9592 100644 --- a/src/Symfony/Component/Form/Tests/CallbackTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/CallbackTransformerTest.php @@ -19,8 +19,8 @@ class CallbackTransformerTest extends TestCase public function testTransform() { $transformer = new CallbackTransformer( - function ($value) { return $value.' has been transformed'; }, - function ($value) { return $value.' has reversely been transformed'; } + fn ($value) => $value.' has been transformed', + fn ($value) => $value.' has reversely been transformed' ); $this->assertEquals('foo has been transformed', $transformer->transform('foo')); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php index a156ca5b41147..835085a7acb5f 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php @@ -45,9 +45,7 @@ protected function getValues() public function testCreateChoiceListWithValueCallback() { - $callback = function ($choice) { - return ':'.$choice; - }; + $callback = fn ($choice) => ':'.$choice; $choiceList = new ArrayChoiceList([2 => 'foo', 7 => 'bar', 10 => 'baz'], $callback); @@ -112,9 +110,7 @@ public function testCreateChoiceListWithGroupedChoices() public function testCompareChoicesByIdentityByDefault() { - $callback = function ($choice) { - return $choice->value; - }; + $callback = fn ($choice) => $choice->value; $obj1 = (object) ['value' => 'value1']; $obj2 = (object) ['value' => 'value2']; diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/Cache/ChoiceLoaderTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/Cache/ChoiceLoaderTest.php index b2d0d2e6d700e..6134160046ddf 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/Cache/ChoiceLoaderTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/Cache/ChoiceLoaderTest.php @@ -26,9 +26,7 @@ public function testSameFormTypeUseCachedLoader() $choiceList = new ArrayChoiceList($choices); $type = new FormType(); - $decorated = new CallbackChoiceLoader(static function () use ($choices) { - return $choices; - }); + $decorated = new CallbackChoiceLoader(static fn () => $choices); $loader1 = new ChoiceLoader($type, $decorated); $loader2 = new ChoiceLoader($type, new ArrayChoiceLoader()); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php index 893af48593ebf..d4231bf946a2f 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php @@ -146,9 +146,7 @@ public function testCreateFromChoicesSameFilterClosure() $filter = function () {}; $list1 = $this->factory->createListFromChoices($choices, null, $filter); $list2 = $this->factory->createListFromChoices($choices, null, $filter); - $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static function () use ($choices) { - return $choices; - }), $filter), null); + $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static fn () => $choices), $filter), null); $this->assertNotSame($list1, $list2); $this->assertEquals($lazyChoiceList, $list1); @@ -162,9 +160,7 @@ public function testCreateFromChoicesSameFilterClosureUseCache() $filterCallback = function () {}; $list1 = $this->factory->createListFromChoices($choices, null, ChoiceList::filter($formType, $filterCallback)); $list2 = $this->factory->createListFromChoices($choices, null, ChoiceList::filter($formType, function () {})); - $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static function () use ($choices) { - return $choices; - }), function () {}), null); + $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static fn () => $choices), function () {}), null); $this->assertSame($list1, $list2); $this->assertEquals($lazyChoiceList, $list1); @@ -178,9 +174,7 @@ public function testCreateFromChoicesDifferentFilterClosure() $closure2 = function () {}; $list1 = $this->factory->createListFromChoices($choices, null, $closure1); $list2 = $this->factory->createListFromChoices($choices, null, $closure2); - $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static function () use ($choices) { - return $choices; - }), function () {}), null); + $lazyChoiceList = new LazyChoiceList(new FilterChoiceLoaderDecorator(new CallbackChoiceLoader(static fn () => $choices), function () {}), null); $this->assertNotSame($list1, $list2); $this->assertEquals($lazyChoiceList, $list1); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index 054739c8ccd80..dfbbd2b5b8166 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -151,7 +151,7 @@ public function testCreateFromChoicesFlatValuesAsClosure() { $list = $this->factory->createListFromChoices( ['A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4], - function ($object) { return $object->value; } + fn ($object) => $object->value ); $this->assertObjectListWithCustomValues($list); @@ -199,7 +199,7 @@ public function testCreateFromChoicesGroupedValuesAsClosure() 'Group 1' => ['A' => $this->obj1, 'B' => $this->obj2], 'Group 2' => ['C' => $this->obj3, 'D' => $this->obj4], ], - function ($object) { return $object->value; } + fn ($object) => $object->value ); $this->assertObjectListWithCustomValues($list); @@ -210,9 +210,7 @@ public function testCreateFromFilteredChoices() $list = $this->factory->createListFromChoices( ['A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4, 'E' => $this->obj5, 'F' => $this->obj6], null, - function ($choice) { - return $choice !== $this->obj5 && $choice !== $this->obj6; - } + fn ($choice) => $choice !== $this->obj5 && $choice !== $this->obj6 ); $this->assertObjectListWithGeneratedValues($list); @@ -228,9 +226,7 @@ public function testCreateFromChoicesGroupedAndFiltered() 'Group 4' => [/* empty group should be filtered */], ], null, - function ($choice) { - return $choice !== $this->obj5 && $choice !== $this->obj6; - } + fn ($choice) => $choice !== $this->obj5 && $choice !== $this->obj6 ); $this->assertObjectListWithGeneratedValues($list); @@ -246,9 +242,7 @@ public function testCreateFromChoicesGroupedAndFilteredTraversable() 'Group 4' => [/* empty group should be filtered */], ]), null, - function ($choice) { - return $choice !== $this->obj5 && $choice !== $this->obj6; - } + fn ($choice) => $choice !== $this->obj5 && $choice !== $this->obj6 ); $this->assertObjectListWithGeneratedValues($list); @@ -313,9 +307,7 @@ public function testCreateViewFlatPreferredChoicesSameOrder() [$this->obj2, $this->obj1, $this->obj4, $this->obj3] ); - $preferredLabels = array_map(static function (ChoiceView $view): string { - return $view->label; - }, $view->preferredChoices); + $preferredLabels = array_map(static fn (ChoiceView $view): string => $view->label, $view->preferredChoices); $this->assertSame( [ @@ -338,11 +330,7 @@ public function testCreateViewFlatPreferredChoiceGroupsSameOrder() $this->getGroup(...) ); - $preferredLabels = array_map(static function (ChoiceGroupView $groupView): array { - return array_map(static function (ChoiceView $view): string { - return $view->label; - }, $groupView->choices); - }, $view->preferredChoices); + $preferredLabels = array_map(static fn (ChoiceGroupView $groupView): array => array_map(static fn (ChoiceView $view): string => $view->label, $groupView->choices), $view->preferredChoices); $this->assertEquals( [ @@ -393,9 +381,7 @@ public function testCreateViewFlatPreferredChoicesAsClosure() $view = $this->factory->createView( $this->list, - function ($object) use ($obj2, $obj3) { - return $obj2 === $object || $obj3 === $object; - } + fn ($object) => $obj2 === $object || $obj3 === $object ); $this->assertFlatView($view); @@ -405,9 +391,7 @@ public function testCreateViewFlatPreferredChoicesClosureReceivesKey() { $view = $this->factory->createView( $this->list, - function ($object, $key) { - return 'B' === $key || 'C' === $key; - } + fn ($object, $key) => 'B' === $key || 'C' === $key ); $this->assertFlatView($view); @@ -417,9 +401,7 @@ public function testCreateViewFlatPreferredChoicesClosureReceivesValue() { $view = $this->factory->createView( $this->list, - function ($object, $key, $value) { - return '1' === $value || '2' === $value; - } + fn ($object, $key, $value) => '1' === $value || '2' === $value ); $this->assertFlatView($view); @@ -441,9 +423,7 @@ public function testCreateViewFlatLabelAsClosure() $view = $this->factory->createView( $this->list, [$this->obj2, $this->obj3], - function ($object) { - return $object->label; - } + fn ($object) => $object->label ); $this->assertFlatView($view); @@ -454,9 +434,7 @@ public function testCreateViewFlatLabelClosureReceivesKey() $view = $this->factory->createView( $this->list, [$this->obj2, $this->obj3], - function ($object, $key) { - return $key; - } + fn ($object, $key) => $key ); $this->assertFlatView($view); @@ -498,9 +476,7 @@ public function testCreateViewFlatIndexAsClosure() $this->list, [$this->obj2, $this->obj3], null, // label - function ($object) { - return $object->index; - } + fn ($object) => $object->index ); $this->assertFlatViewWithCustomIndices($view); @@ -622,9 +598,7 @@ public function testCreateViewFlatGroupByAsClosure() [$this->obj2, $this->obj3], null, // label null, // index - function ($object) use ($obj1, $obj2) { - return $obj1 === $object || $obj2 === $object ? 'Group 1' : 'Group 2'; - } + fn ($object) => $obj1 === $object || $obj2 === $object ? 'Group 1' : 'Group 2' ); $this->assertGroupedView($view); @@ -637,9 +611,7 @@ public function testCreateViewFlatGroupByClosureReceivesKey() [$this->obj2, $this->obj3], null, // label null, // index - function ($object, $key) { - return 'A' === $key || 'B' === $key ? 'Group 1' : 'Group 2'; - } + fn ($object, $key) => 'A' === $key || 'B' === $key ? 'Group 1' : 'Group 2' ); $this->assertGroupedView($view); @@ -652,9 +624,7 @@ public function testCreateViewFlatGroupByClosureReceivesValue() [$this->obj2, $this->obj3], null, // label null, // index - function ($object, $key, $value) { - return '0' === $value || '1' === $value ? 'Group 1' : 'Group 2'; - } + fn ($object, $key, $value) => '0' === $value || '1' === $value ? 'Group 1' : 'Group 2' ); $this->assertGroupedView($view); @@ -713,9 +683,7 @@ public function testCreateViewFlatAttrAsClosure() null, // label null, // index null, // group - function ($object) { - return $object->attr; - } + fn ($object) => $object->attr ); $this->assertFlatViewWithAttr($view); @@ -729,12 +697,10 @@ public function testCreateViewFlatAttrClosureReceivesKey() null, // label null, // index null, // group - function ($object, $key) { - return match ($key) { - 'B' => ['attr1' => 'value1'], - 'C' => ['attr2' => 'value2'], - default => [], - }; + fn ($object, $key) => match ($key) { + 'B' => ['attr1' => 'value1'], + 'C' => ['attr2' => 'value2'], + default => [], } ); @@ -749,12 +715,10 @@ public function testCreateViewFlatAttrClosureReceivesValue() null, // label null, // index null, // group - function ($object, $key, $value) { - return match ($value) { - '1' => ['attr1' => 'value1'], - '2' => ['attr2' => 'value2'], - default => [], - }; + fn ($object, $key, $value) => match ($value) { + '1' => ['attr1' => 'value1'], + '2' => ['attr2' => 'value2'], + default => [], } ); @@ -766,9 +730,7 @@ public function testPassTranslatableMessageAsLabelDoesntCastItToString() $view = $this->factory->createView( $this->list, [$this->obj1], - static function ($choice, $key, $value) { - return new TranslatableMessage('my_message', ['param1' => 'value1']); - } + static fn ($choice, $key, $value) => new TranslatableMessage('my_message', ['param1' => 'value1']) ); $this->assertInstanceOf(TranslatableMessage::class, $view->choices[0]->label); @@ -788,9 +750,7 @@ public function trans(TranslatorInterface $translator, string $locale = null): s $view = $this->factory->createView( $this->list, [$this->obj1], - static function () use ($message) { - return $message; - } + static fn () => $message ); $this->assertSame($message, $view->choices[0]->label); @@ -852,9 +812,7 @@ public function testCreateViewFlatlabelTranslationParametersAsClosure() null, // index null, // group null, // attr - function ($object) { - return $object->labelTranslationParameters; - } + fn ($object) => $object->labelTranslationParameters ); $this->assertFlatViewWithlabelTranslationParameters($view); @@ -869,11 +827,9 @@ public function testCreateViewFlatlabelTranslationParametersClosureReceivesKey() null, // index null, // group null, // attr - function ($object, $key) { - return match ($key) { - 'D' => ['%placeholder1%' => 'value1'], - default => [], - }; + fn ($object, $key) => match ($key) { + 'D' => ['%placeholder1%' => 'value1'], + default => [], } ); @@ -889,36 +845,15 @@ public function testCreateViewFlatlabelTranslationParametersClosureReceivesValue null, // index null, // group null, // attr - function ($object, $key, $value) { - return match ($value) { - '3' => ['%placeholder1%' => 'value1'], - default => [], - }; + fn ($object, $key, $value) => match ($value) { + '3' => ['%placeholder1%' => 'value1'], + default => [], } ); $this->assertFlatViewWithlabelTranslationParameters($view); } - private function assertScalarListWithChoiceValues(ChoiceListInterface $list) - { - $this->assertSame(['a', 'b', 'c', 'd'], $list->getValues()); - - $this->assertSame([ - 'a' => 'a', - 'b' => 'b', - 'c' => 'c', - 'd' => 'd', - ], $list->getChoices()); - - $this->assertSame([ - 'a' => 'A', - 'b' => 'B', - 'c' => 'C', - 'd' => 'D', - ], $list->getOriginalKeys()); - } - private function assertObjectListWithGeneratedValues(ChoiceListInterface $list) { $this->assertSame(['0', '1', '2', '3'], $list->getValues()); @@ -938,25 +873,6 @@ private function assertObjectListWithGeneratedValues(ChoiceListInterface $list) ], $list->getOriginalKeys()); } - private function assertScalarListWithCustomValues(ChoiceListInterface $list) - { - $this->assertSame(['a', 'b', '1', '2'], $list->getValues()); - - $this->assertSame([ - 'a' => 'a', - 'b' => 'b', - 1 => 'c', - 2 => 'd', - ], $list->getChoices()); - - $this->assertSame([ - 'a' => 'A', - 'b' => 'B', - 1 => 'C', - 2 => 'D', - ], $list->getOriginalKeys()); - } - private function assertObjectListWithCustomValues(ChoiceListInterface $list) { $this->assertSame(['a', 'b', '1', '2'], $list->getValues()); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php index 94d41cf9e740f..501f4377ad559 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php @@ -108,9 +108,7 @@ public function testGetChoicesForValuesUsesLoadedList() 'b' => 'bar', 'c' => 'baz', ]; - $list = new LazyChoiceList(new ArrayChoiceLoader($choices), function ($choice) use ($choices) { - return array_search($choice, $choices); - }); + $list = new LazyChoiceList(new ArrayChoiceLoader($choices), fn ($choice) => array_search($choice, $choices)); // load choice list $list->getChoices(); @@ -126,9 +124,7 @@ public function testGetValuesForChoicesUsesLoadedList() 'b' => 'bar', 'c' => 'baz', ]; - $list = new LazyChoiceList(new ArrayChoiceLoader($choices), function ($choice) use ($choices) { - return array_search($choice, $choices); - }); + $list = new LazyChoiceList(new ArrayChoiceLoader($choices), fn ($choice) => array_search($choice, $choices)); // load choice list $list->getChoices(); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/FilterChoiceLoaderDecoratorTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/FilterChoiceLoaderDecoratorTest.php index 1f91a47275a33..5a41e5aff3415 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/FilterChoiceLoaderDecoratorTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/FilterChoiceLoaderDecoratorTest.php @@ -20,9 +20,7 @@ class FilterChoiceLoaderDecoratorTest extends TestCase { public function testLoadChoiceList() { - $filter = function ($choice) { - return 0 === $choice % 2; - }; + $filter = fn ($choice) => 0 === $choice % 2; $loader = new FilterChoiceLoaderDecorator(new ArrayChoiceLoader(range(1, 4)), $filter); @@ -31,9 +29,7 @@ public function testLoadChoiceList() public function testLoadChoiceListWithGroupedChoices() { - $filter = function ($choice) { - return $choice < 9 && 0 === $choice % 2; - }; + $filter = fn ($choice) => $choice < 9 && 0 === $choice % 2; $loader = new FilterChoiceLoaderDecorator(new ArrayChoiceLoader(['units' => range(1, 9), 'tens' => range(10, 90, 10)]), $filter); @@ -49,9 +45,7 @@ public function testLoadChoiceListWithGroupedChoices() public function testLoadChoiceListMixedWithGroupedAndNonGroupedChoices() { - $filter = function ($choice) { - return 0 === $choice % 2; - }; + $filter = fn ($choice) => 0 === $choice % 2; $choices = array_merge(range(1, 9), ['grouped' => range(10, 40, 5)]); $loader = new FilterChoiceLoaderDecorator(new ArrayChoiceLoader($choices), $filter); @@ -74,9 +68,7 @@ public function testLoadValuesForChoices() { $evenValues = [1 => '2', 3 => '4']; - $filter = function ($choice) { - return 0 === $choice % 2; - }; + $filter = fn ($choice) => 0 === $choice % 2; $loader = new FilterChoiceLoaderDecorator(new ArrayChoiceLoader([range(1, 4)]), $filter); @@ -88,9 +80,7 @@ public function testLoadChoicesForValues() $evenChoices = [1 => 2, 3 => 4]; $values = array_map('strval', range(1, 4)); - $filter = function ($choice) { - return 0 === $choice % 2; - }; + $filter = fn ($choice) => 0 === $choice % 2; $loader = new FilterChoiceLoaderDecorator(new ArrayChoiceLoader(range(1, 4)), $filter); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php index e2827b0d913be..0aed92fb5e901 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php @@ -49,12 +49,8 @@ class IntlCallbackChoiceLoaderTest extends TestCase public static function setUpBeforeClass(): void { - self::$loader = new IntlCallbackChoiceLoader(function () { - return self::$choices; - }); - self::$value = function ($choice) { - return $choice->value ?? null; - }; + self::$loader = new IntlCallbackChoiceLoader(fn () => self::$choices); + self::$value = fn ($choice) => $choice->value ?? null; self::$choices = [ (object) ['value' => 'choice_one'], (object) ['value' => 'choice_two'], diff --git a/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php index cc23ce5f4b9d2..4537099c2dc47 100644 --- a/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php @@ -265,7 +265,7 @@ private static function getCoreTypes(): array $coreExtension = new CoreExtension(); $loadTypesRefMethod = (new \ReflectionObject($coreExtension))->getMethod('loadTypes'); $coreTypes = $loadTypesRefMethod->invoke($coreExtension); - $coreTypes = array_map(function (FormTypeInterface $type) { return $type::class; }, $coreTypes); + $coreTypes = array_map(fn (FormTypeInterface $type) => $type::class, $coreTypes); sort($coreTypes); return $coreTypes; @@ -284,7 +284,7 @@ private function createCommandTester(array $namespaces = ['Symfony\Component\For class FooType extends AbstractType { - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setRequired('foo'); $resolver->setDefined('bar'); @@ -292,15 +292,11 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setDefault('empty_data', function (Options $options) { $foo = $options['foo']; - return function (FormInterface $form) use ($foo) { - return $form->getConfig()->getCompound() ? [$foo] : $foo; - }; + return fn (FormInterface $form) => $form->getConfig()->getCompound() ? [$foo] : $foo; }); $resolver->setAllowedTypes('foo', 'string'); $resolver->setAllowedValues('foo', ['bar', 'baz']); - $resolver->setNormalizer('foo', function (Options $options, $value) { - return (string) $value; - }); + $resolver->setNormalizer('foo', fn (Options $options, $value) => (string) $value); $resolver->setInfo('foo', 'Info'); } } diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index 0885bef8abdff..5247cdecdb820 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -955,10 +955,8 @@ public function testCreateViewWithChildren() $this->form->add($field1); $this->form->add($field2); - $assertChildViewsEqual = function (array $childViews) { - return function (FormView $view) use ($childViews) { - $this->assertSame($childViews, $view->children); - }; + $assertChildViewsEqual = fn (array $childViews) => function (FormView $view) use ($childViews) { + $this->assertSame($childViews, $view->children); }; // First create the view diff --git a/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTestCase.php b/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTestCase.php index f11cb8c5056a8..3201ab9f72770 100644 --- a/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTestCase.php +++ b/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTestCase.php @@ -159,7 +159,7 @@ private function getFixtureFilename($name) class FooType extends AbstractType { - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setRequired('foo'); $resolver->setDefined('bar'); @@ -167,14 +167,10 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setDefault('empty_data', function (Options $options, $value) { $foo = $options['foo']; - return function (FormInterface $form) use ($foo) { - return $form->getConfig()->getCompound() ? [$foo] : $foo; - }; + return fn (FormInterface $form) => $form->getConfig()->getCompound() ? [$foo] : $foo; }); $resolver->setAllowedTypes('foo', 'string'); $resolver->setAllowedValues('foo', ['bar', 'baz']); - $resolver->setNormalizer('foo', function (Options $options, $value) { - return (string) $value; - }); + $resolver->setNormalizer('foo', fn (Options $options, $value) => (string) $value); } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/CoreExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/CoreExtensionTest.php index ff85149e21c63..91b1e75086324 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/CoreExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/CoreExtensionTest.php @@ -24,7 +24,7 @@ public function testTransformationFailuresAreConvertedIntoFormErrors() ->getFormFactory(); $form = $formFactory->createBuilder() - ->add('foo', 'Symfony\Component\Form\Extension\Core\Type\DateType') + ->add('foo', 'Symfony\Component\Form\Extension\Core\Type\DateType', ['widget' => 'choice']) ->getForm(); $form->submit('foo'); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php index 6a2fc6edf1325..7dcf4169bac94 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/DataMapperTest.php @@ -359,9 +359,7 @@ public function testMapDataToFormsUsingGetCallbackOption() $person = new DummyPerson($initialName); $config = new FormConfigBuilder('name', null, $this->dispatcher, [ - 'getter' => static function (DummyPerson $person) { - return $person->myName(); - }, + 'getter' => static fn (DummyPerson $person) => $person->myName(), ]); $form = new Form($config); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php index 8dffb13e2f927..bcea2b829616e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php @@ -23,12 +23,16 @@ class DateTimeToHtml5LocalDateTimeTransformerTest extends BaseDateTimeTransforme public static function transformProvider() { return [ - ['UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06'], - ['UTC', 'UTC', null, ''], - ['America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05:06'], - ['America/New_York', 'Asia/Hong_Kong', null, ''], - ['UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05:06'], - ['America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06'], + ['UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06', true], + ['UTC', 'UTC', null, '', true], + ['America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05:06', true], + ['America/New_York', 'Asia/Hong_Kong', null, '', true], + ['UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05:06', true], + ['America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06', true], + ['UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05', false], + ['America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05', false], + ['UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05', false], + ['America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05', false], ]; } @@ -55,9 +59,9 @@ public static function reverseTransformProvider() /** * @dataProvider transformProvider */ - public function testTransform($fromTz, $toTz, $from, $to) + public function testTransform($fromTz, $toTz, $from, $to, bool $withSeconds) { - $transformer = new DateTimeToHtml5LocalDateTimeTransformer($fromTz, $toTz); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer($fromTz, $toTz, $withSeconds); $this->assertSame($to, $transformer->transform(null !== $from ? new \DateTime($from) : null)); } @@ -65,9 +69,9 @@ public function testTransform($fromTz, $toTz, $from, $to) /** * @dataProvider transformProvider */ - public function testTransformDateTimeImmutable($fromTz, $toTz, $from, $to) + public function testTransformDateTimeImmutable($fromTz, $toTz, $from, $to, bool $withSeconds) { - $transformer = new DateTimeToHtml5LocalDateTimeTransformer($fromTz, $toTz); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer($fromTz, $toTz, $withSeconds); $this->assertSame($to, $transformer->transform(null !== $from ? new \DateTimeImmutable($from) : null)); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php index 9bcb22efe0473..d42d4d8899585 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php @@ -285,9 +285,7 @@ public function testOnSubmitDeleteEmptyCompoundEntriesIfAllowDelete() $this->form->get($child)->submit($dat); } $event = new FormEvent($this->form, $data); - $callback = function ($data) { - return null === $data['name']; - }; + $callback = fn ($data) => null === $data['name']; $listener = new ResizeFormListener('text', [], false, true, $callback); $listener->onSubmit($event); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/BirthdayTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/BirthdayTypeTest.php index 47028ac014a75..0484571411804 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/BirthdayTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/BirthdayTypeTest.php @@ -25,6 +25,7 @@ public function testSetInvalidYearsOption() $this->expectException(InvalidOptionsException::class); $this->factory->create(static::TESTED_TYPE, null, [ 'years' => 'bad value', + 'widget' => 'choice', ]); } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php index 2cb20e1cbb6c5..9c5244bd3afc7 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php @@ -146,12 +146,8 @@ public function testCustomModelTransformer($data, $checked) { // present a binary status field as a checkbox $transformer = new CallbackTransformer( - function ($value) { - return 'checked' == $value; - }, - function ($value) { - return $value ? 'checked' : 'unchecked'; - } + fn ($value) => 'checked' == $value, + fn ($value) => $value ? 'checked' : 'unchecked' ); $form = $this->factory->createBuilder(static::TESTED_TYPE) diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 73c12b50a408a..11c389551b825 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -100,6 +100,14 @@ public function testChoiceLoaderOptionExpectsChoiceLoaderInterface() ]); } + public function testPlaceholderAttrOptionExpectsArray() + { + $this->expectException(InvalidOptionsException::class); + $this->factory->create(static::TESTED_TYPE, null, [ + 'placeholder_attr' => new \stdClass(), + ]); + } + public function testChoiceListAndChoicesCanBeEmpty() { $this->assertInstanceOf(FormInterface::class, $this->factory->create(static::TESTED_TYPE, null, [])); @@ -189,15 +197,19 @@ public function testExpandedChoiceListWithBooleanAndNullValuesAndFalseAsPreSetDa public function testPlaceholderPresentOnNonRequiredExpandedSingleChoice() { + $placeholderAttr = ['attr' => 'value']; + $form = $this->factory->create(static::TESTED_TYPE, null, [ 'multiple' => false, 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'placeholder_attr' => $placeholderAttr, ]); $this->assertArrayHasKey('placeholder', $form); $this->assertCount(\count($this->choices) + 1, $form, 'Each choice should become a new field'); + $this->assertSame($placeholderAttr, $form->createView()->children['placeholder']->vars['attr']); } public function testPlaceholderNotPresentIfRequired() @@ -531,9 +543,7 @@ public function testSubmitSingleNonExpandedEmptyExplicitEmptyChoice() 'choices' => [ 'Empty' => 'EMPTY_CHOICE', ], - 'choice_value' => function () { - return ''; - }, + 'choice_value' => fn () => '', ]); $form->submit(''); @@ -1669,36 +1679,40 @@ public function testPlaceholderIsEmptyStringByDefaultIfNotRequired() /** * @dataProvider getOptionsWithPlaceholder */ - public function testPassPlaceholderToView($multiple, $expanded, $required, $placeholder, $viewValue) + public function testPassPlaceholderToView($multiple, $expanded, $required, $placeholder, $placeholderViewValue, $placeholderAttr, $placeholderAttrViewValue) { $view = $this->factory->create(static::TESTED_TYPE, null, [ 'multiple' => $multiple, 'expanded' => $expanded, 'required' => $required, 'placeholder' => $placeholder, + 'placeholder_attr' => $placeholderAttr, 'choices' => $this->choices, ]) ->createView(); - $this->assertSame($viewValue, $view->vars['placeholder']); + $this->assertSame($placeholderViewValue, $view->vars['placeholder']); + $this->assertSame($placeholderAttrViewValue, $view->vars['placeholder_attr']); $this->assertFalse($view->vars['placeholder_in_choices']); } /** * @dataProvider getOptionsWithPlaceholder */ - public function testDontPassPlaceholderIfContainedInChoices($multiple, $expanded, $required, $placeholder, $viewValue) + public function testDontPassPlaceholderIfContainedInChoices($multiple, $expanded, $required, $placeholder, $placeholderViewValue, $placeholderAttr, $placeholderAttrViewValue) { $view = $this->factory->create(static::TESTED_TYPE, null, [ 'multiple' => $multiple, 'expanded' => $expanded, 'required' => $required, 'placeholder' => $placeholder, + 'placeholder_attr' => $placeholderAttr, 'choices' => ['Empty' => '', 'A' => 'a'], ]) ->createView(); $this->assertNull($view->vars['placeholder']); + $this->assertSame([], $view->vars['placeholder_attr']); $this->assertTrue($view->vars['placeholder_in_choices']); } @@ -1706,43 +1720,43 @@ public static function getOptionsWithPlaceholder() { return [ // single non-expanded - [false, false, false, 'foobar', 'foobar'], - [false, false, false, '', ''], - [false, false, false, null, null], - [false, false, false, false, null], - [false, false, true, 'foobar', 'foobar'], - [false, false, true, '', ''], - [false, false, true, null, null], - [false, false, true, false, null], + [false, false, false, 'foobar', 'foobar', ['attr' => 'value'], ['attr' => 'value']], + [false, false, false, '', '', ['attr' => 'value'], ['attr' => 'value']], + [false, false, false, null, null, ['attr' => 'value'], []], + [false, false, false, false, null, ['attr' => 'value'], []], + [false, false, true, 'foobar', 'foobar', ['attr' => 'value'], ['attr' => 'value']], + [false, false, true, '', '', ['attr' => 'value'], ['attr' => 'value']], + [false, false, true, null, null, ['attr' => 'value'], []], + [false, false, true, false, null, ['attr' => 'value'], []], // single expanded - [false, true, false, 'foobar', 'foobar'], + [false, true, false, 'foobar', 'foobar', ['attr' => 'value'], ['attr' => 'value']], // radios should never have an empty label - [false, true, false, '', 'None'], - [false, true, false, null, null], - [false, true, false, false, null], + [false, true, false, '', 'None', ['attr' => 'value'], ['attr' => 'value']], + [false, true, false, null, null, ['attr' => 'value'], []], + [false, true, false, false, null, ['attr' => 'value'], []], // required radios should never have a placeholder - [false, true, true, 'foobar', null], - [false, true, true, '', null], - [false, true, true, null, null], - [false, true, true, false, null], + [false, true, true, 'foobar', null, ['attr' => 'value'], []], + [false, true, true, '', null, ['attr' => 'value'], []], + [false, true, true, null, null, ['attr' => 'value'], []], + [false, true, true, false, null, ['attr' => 'value'], []], // multiple non-expanded - [true, false, false, 'foobar', null], - [true, false, false, '', null], - [true, false, false, null, null], - [true, false, false, false, null], - [true, false, true, 'foobar', null], - [true, false, true, '', null], - [true, false, true, null, null], - [true, false, true, false, null], + [true, false, false, 'foobar', null, ['attr' => 'value'], []], + [true, false, false, '', null, ['attr' => 'value'], []], + [true, false, false, null, null, ['attr' => 'value'], []], + [true, false, false, false, null, ['attr' => 'value'], []], + [true, false, true, 'foobar', null, ['attr' => 'value'], []], + [true, false, true, '', null, ['attr' => 'value'], []], + [true, false, true, null, null, ['attr' => 'value'], []], + [true, false, true, false, null, ['attr' => 'value'], []], // multiple expanded - [true, true, false, 'foobar', null], - [true, true, false, '', null], - [true, true, false, null, null], - [true, true, false, false, null], - [true, true, true, 'foobar', null], - [true, true, true, '', null], - [true, true, true, null, null], - [true, true, true, false, null], + [true, true, false, 'foobar', null, ['attr' => 'value'], []], + [true, true, false, '', null, ['attr' => 'value'], []], + [true, true, false, null, null, ['attr' => 'value'], []], + [true, true, false, false, null, ['attr' => 'value'], []], + [true, true, true, 'foobar', null, ['attr' => 'value'], []], + [true, true, true, '', null, ['attr' => 'value'], []], + [true, true, true, null, null, ['attr' => 'value'], []], + [true, true, true, false, null, ['attr' => 'value'], []], ]; } @@ -2208,9 +2222,7 @@ public function testFilteredChoices() { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'choices' => $this->choices, - 'choice_filter' => function ($choice) { - return \in_array($choice, range('a', 'c'), true); - }, + 'choice_filter' => fn ($choice) => \in_array($choice, range('a', 'c'), true), ]); $this->assertEquals([ @@ -2224,9 +2236,7 @@ public function testFilteredGroupedChoices() { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'choices' => $this->groupedChoices, - 'choice_filter' => function ($choice) { - return \in_array($choice, range('a', 'c'), true); - }, + 'choice_filter' => fn ($choice) => \in_array($choice, range('a', 'c'), true), ]); $this->assertEquals(['Symfony' => new ChoiceGroupView('Symfony', [ @@ -2239,12 +2249,8 @@ public function testFilteredGroupedChoices() public function testFilteredChoiceLoader() { $form = $this->factory->create(static::TESTED_TYPE, null, [ - 'choice_loader' => new CallbackChoiceLoader(function () { - return $this->choices; - }), - 'choice_filter' => function ($choice) { - return \in_array($choice, range('a', 'c'), true); - }, + 'choice_loader' => new CallbackChoiceLoader(fn () => $this->choices), + 'choice_filter' => fn ($choice) => \in_array($choice, range('a', 'c'), true), ]); $this->assertEquals([ @@ -2256,9 +2262,7 @@ public function testFilteredChoiceLoader() public function testWithSameLoaderAndDifferentChoiceValueCallbacks() { - $choiceLoader = new CallbackChoiceLoader(function () { - return [1, 2, 3]; - }); + $choiceLoader = new CallbackChoiceLoader(fn () => [1, 2, 3]); $view = $this->factory->create(FormTypeTest::TESTED_TYPE) ->add('choice_one', self::TESTED_TYPE, [ @@ -2266,9 +2270,7 @@ public function testWithSameLoaderAndDifferentChoiceValueCallbacks() ]) ->add('choice_two', self::TESTED_TYPE, [ 'choice_loader' => $choiceLoader, - 'choice_value' => function ($choice) { - return $choice ? (string) $choice * 10 : ''; - }, + 'choice_value' => fn ($choice) => $choice ? (string) $choice * 10 : '', ]) ->createView() ; diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php index 9e144100590ac..490e84604aa15 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTranslationTest.php @@ -31,9 +31,7 @@ protected function getExtensions() { $translator = $this->createMock(TranslatorInterface::class); $translator->expects($this->any())->method('trans') - ->willReturnCallback(function ($key, $params) { - return strtr(sprintf('Translation of: %s', $key), $params); - } + ->willReturnCallback(fn ($key, $params) => strtr(sprintf('Translation of: %s', $key), $params) ); return array_merge(parent::getExtensions(), [new CoreExtension(null, null, $translator)]); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php index 81626110481b1..dd92b7c89e11d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php @@ -120,9 +120,7 @@ public function testResizedDownWithDeleteEmptyCallable() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'entry_type' => AuthorType::class, 'allow_delete' => true, - 'delete_empty' => function (Author $obj = null) { - return null === $obj || empty($obj->firstName); - }, + 'delete_empty' => fn (Author $obj = null) => null === $obj || empty($obj->firstName), ]); $form->setData([new Author('Bob'), new Author('Alice')]); @@ -143,9 +141,7 @@ public function testResizedDownIfSubmittedWithCompoundEmptyDataDeleteEmptyAndNoD 'entry_options' => ['data_class' => null], 'allow_add' => true, 'allow_delete' => true, - 'delete_empty' => function ($author) { - return empty($author['firstName']); - }, + 'delete_empty' => fn ($author) => empty($author['firstName']), ]); $form->setData([['firstName' => 'first', 'lastName' => 'last']]); $form->submit([ diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php index 5a35abb453662..a2058596eeeee 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -252,7 +252,7 @@ public function testSubmitDifferentTimezonesDateTime() $outputTime->setTimezone(new \DateTimeZone('America/New_York')); $this->assertEquals($outputTime, $form->getData()); - $this->assertEquals('2010-06-02T03:04:00', $form->getViewData()); + $this->assertEquals('2010-06-02T03:04', $form->getViewData()); } public function testSubmitDifferentTimezonesDateTimeImmutable() @@ -272,7 +272,7 @@ public function testSubmitDifferentTimezonesDateTimeImmutable() $this->assertInstanceOf(\DateTimeImmutable::class, $form->getData()); $this->assertEquals($outputTime, $form->getData()); - $this->assertEquals('2010-06-02T03:04:00', $form->getViewData()); + $this->assertEquals('2010-06-02T03:04', $form->getViewData()); } public function testSubmitStringSingleText() @@ -287,7 +287,7 @@ public function testSubmitStringSingleText() $form->submit('2010-06-02T03:04:00'); $this->assertEquals('2010-06-02 03:04:00', $form->getData()); - $this->assertEquals('2010-06-02T03:04:00', $form->getViewData()); + $this->assertEquals('2010-06-02T03:04', $form->getViewData()); } public function testSubmitStringSingleTextWithSeconds() @@ -330,7 +330,7 @@ public function testInitializeWithDateTime() { // Throws an exception if "data_class" option is not explicitly set // to null in the type - $this->assertInstanceOf(FormInterface::class, $this->factory->create(static::TESTED_TYPE, new \DateTime())); + $this->assertInstanceOf(FormInterface::class, $this->factory->create(static::TESTED_TYPE, new \DateTime(), ['widget' => 'choice'])); } public function testSingleTextWidgetShouldUseTheRightInputType() @@ -348,6 +348,7 @@ public function testPassDefaultPlaceholderToViewIfNotRequired() $view = $this->factory->create(static::TESTED_TYPE, null, [ 'required' => false, 'with_seconds' => true, + 'widget' => 'choice', ]) ->createView(); @@ -364,6 +365,7 @@ public function testPassNoPlaceholderToViewIfRequired() $view = $this->factory->create(static::TESTED_TYPE, null, [ 'required' => true, 'with_seconds' => true, + 'widget' => 'choice', ]) ->createView(); @@ -380,6 +382,7 @@ public function testPassPlaceholderAsString() $view = $this->factory->create(static::TESTED_TYPE, null, [ 'placeholder' => 'Empty', 'with_seconds' => true, + 'widget' => 'choice', ]) ->createView(); @@ -403,6 +406,7 @@ public function testPassPlaceholderAsArray() 'second' => 'Empty second', ], 'with_seconds' => true, + 'widget' => 'choice', ]) ->createView(); @@ -425,6 +429,7 @@ public function testPassPlaceholderAsPartialArrayAddEmptyIfNotRequired() 'second' => 'Empty second', ], 'with_seconds' => true, + 'widget' => 'choice', ]) ->createView(); @@ -447,6 +452,7 @@ public function testPassPlaceholderAsPartialArrayAddNullIfRequired() 'second' => 'Empty second', ], 'with_seconds' => true, + 'widget' => 'choice', ]) ->createView(); @@ -536,7 +542,7 @@ public function testSingleTextWidgetWithCustomNonHtml5Format() public function testDateTypeChoiceErrorsBubbleUp() { $error = new FormError('Invalid!'); - $form = $this->factory->create(static::TESTED_TYPE, null); + $form = $this->factory->create(static::TESTED_TYPE, null, ['widget' => 'choice']); $form['date']->addError($error); @@ -549,6 +555,7 @@ public function testDateTypeSingleTextErrorsBubbleUp() $error = new FormError('Invalid!'); $form = $this->factory->create(static::TESTED_TYPE, null, [ 'date_widget' => 'single_text', + 'time_widget' => 'choice', ]); $form['date']->addError($error); @@ -560,7 +567,7 @@ public function testDateTypeSingleTextErrorsBubbleUp() public function testTimeTypeChoiceErrorsBubbleUp() { $error = new FormError('Invalid!'); - $form = $this->factory->create(static::TESTED_TYPE, null); + $form = $this->factory->create(static::TESTED_TYPE, null, ['widget' => 'choice']); $form['time']->addError($error); @@ -573,6 +580,7 @@ public function testTimeTypeSingleTextErrorsBubbleUp() $error = new FormError('Invalid!'); $form = $this->factory->create(static::TESTED_TYPE, null, [ 'time_widget' => 'single_text', + 'date_widget' => 'choice', ]); $form['time']->addError($error); @@ -585,6 +593,7 @@ public function testPassDefaultChoiceTranslationDomain() { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -602,6 +611,7 @@ public function testPassChoiceTranslationDomainAsString() $form = $this->factory->create(static::TESTED_TYPE, null, [ 10000 'choice_translation_domain' => 'messages', 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -623,6 +633,7 @@ public function testPassChoiceTranslationDomainAsArray() 'second' => 'test', ], 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -675,6 +686,7 @@ public function testSubmitNullUsesDefaultEmptyData($emptyData = [], $expectedDat { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'empty_data' => $emptyData, + 'widget' => 'choice', ]); $form->submit(null); @@ -709,12 +721,10 @@ public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedDa public static function provideEmptyData() { $expectedData = \DateTime::createFromFormat('Y-m-d H:i', '2018-11-11 21:23'); - $lazyEmptyData = static function (FormInterface $form) { - return $form->getConfig()->getCompound() ? ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']] : '2018-11-11T21:23:00'; - }; + $lazyEmptyData = static fn (FormInterface $form) => $form->getConfig()->getCompound() ? ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']] : '2018-11-11T21:23'; return [ - 'Simple field' => ['single_text', '2018-11-11T21:23:00', $expectedData], + 'Simple field' => ['single_text', '2018-11-11T21:23', $expectedData], 'Compound text field' => ['text', ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']], $expectedData], 'Compound choice field' => ['choice', ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']], $expectedData], 'Simple field lazy' => ['single_text', $lazyEmptyData, $expectedData], @@ -737,4 +747,9 @@ public function testSubmitStringWithCustomInputFormat() $this->assertSame('14/01/2018 21:29:00 +00:00', $form->getData()); } + + protected function getTestOptions(): array + { + return ['widget' => 'choice']; + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php index 32bf613037992..abda1a6c071f2 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -50,6 +50,7 @@ public function testInvalidInputOption() $this->expectException(InvalidOptionsException::class); $this->factory->create(static::TESTED_TYPE, null, [ 'input' => 'fake_input', + 'widget' => 'choice', ]); } @@ -379,6 +380,7 @@ public function testDatePatternWithFormatOption($format, $pattern) { $view = $this->factory->create(static::TESTED_TYPE, null, [ 'format' => $format, + 'widget' => 'choice', ]) ->createView(); @@ -416,6 +418,7 @@ public function testThrowExceptionIfFormatDoesNotContainYearMonthAndDay() $this->factory->create(static::TESTED_TYPE, null, [ 'months' => [6, 7], 'format' => 'yy', + 'widget' => 'choice', ]); } @@ -435,6 +438,7 @@ public function testThrowExceptionIfFormatIsNoConstant() $this->expectException(InvalidOptionsException::class); $this->factory->create(static::TESTED_TYPE, null, [ 'format' => 105, + 'widget' => 'choice', ]); } @@ -443,6 +447,7 @@ public function testThrowExceptionIfFormatIsInvalid() $this->expectException(InvalidOptionsException::class); $this->factory->create(static::TESTED_TYPE, null, [ 'format' => [], + 'widget' => 'choice', ]); } @@ -451,6 +456,7 @@ public function testThrowExceptionIfYearsIsInvalid() $this->expectException(InvalidOptionsException::class); $this->factory->create(static::TESTED_TYPE, null, [ 'years' => 'bad value', + 'widget' => 'choice', ]); } @@ -459,6 +465,7 @@ public function testThrowExceptionIfMonthsIsInvalid() $this->expectException(InvalidOptionsException::class); $this->factory->create(static::TESTED_TYPE, null, [ 'months' => 'bad value', + 'widget' => 'choice', ]); } @@ -467,6 +474,7 @@ public function testThrowExceptionIfDaysIsInvalid() $this->expectException(InvalidOptionsException::class); $this->factory->create(static::TESTED_TYPE, null, [ 'days' => 'bad value', + 'widget' => 'choice', ]); } @@ -523,6 +531,7 @@ public function testYearsOption() { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'years' => [2010, 2011], + 'widget' => 'choice', ]); $view = $form->createView(); @@ -539,6 +548,7 @@ public function testMonthsOption() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'months' => [6, 7], 'format' => \IntlDateFormatter::SHORT, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -559,6 +569,7 @@ public function testMonthsOptionShortFormat() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'months' => [1, 4], 'format' => 'dd.MMM.yy', + 'widget' => 'choice', ]); $view = $form->createView(); @@ -579,6 +590,7 @@ public function testMonthsOptionLongFormat() $view = $this->factory->create(static::TESTED_TYPE, null, [ 'months' => [1, 4], 'format' => 'dd.MMMM.yy', + 'widget' => 'choice', ]) ->createView(); @@ -598,6 +610,7 @@ public function testMonthsOptionLongFormatWithDifferentTimezone() $view = $this->factory->create(static::TESTED_TYPE, null, [ 'months' => [1, 4], 'format' => 'dd.MMMM.yy', + 'widget' => 'choice', ]) ->createView(); @@ -612,6 +625,7 @@ public function testIsDayWithinRangeReturnsTrueIfWithin() \Locale::setDefault('en'); $view = $this->factory->create(static::TESTED_TYPE, null, [ 'days' => [6, 7], + 'widget' => 'choice', ]) ->createView(); @@ -679,7 +693,7 @@ public function testPassDatePatternToView() \Locale::setDefault('de_AT'); - $view = $this->factory->create(static::TESTED_TYPE) + $view = $this->factory->create(static::TESTED_TYPE, null, ['widget' => 'choice']) ->createView(); $this->assertSame('{{ day }}{{ month }}{{ year }}', $view->vars['date_pattern']); @@ -694,6 +708,7 @@ public function testPassDatePatternToViewDifferentFormat() $view = $this->factory->create(static::TESTED_TYPE, null, [ 'format' => \IntlDateFormatter::LONG, + 'widget' => 'choice', ]) ->createView(); @@ -704,6 +719,7 @@ public function testPassDatePatternToViewDifferentPattern() { $view = $this->factory->create(static::TESTED_TYPE, null, [ 'format' => 'MMyyyydd', + 'widget' => 'choice', ]) ->createView(); @@ -714,6 +730,7 @@ public function testPassDatePatternToViewDifferentPatternWithSeparators() { $view = $this->factory->create(static::TESTED_TYPE, null, [ 'format' => 'MM*yyyy*dd', + 'widget' => 'choice', ]) ->createView(); @@ -740,6 +757,7 @@ public function testDatePatternFormatWithQuotedStrings() $view = $this->factory->create(static::TESTED_TYPE, null, [ // EEEE, d 'de' MMMM 'de' y 'format' => \IntlDateFormatter::FULL, + 'widget' => 'choice', ]) ->createView(); @@ -760,7 +778,7 @@ public function testInitializeWithDateTime() { // Throws an exception if "data_class" option is not explicitly set // to null in the type - $this->assertInstanceOf(FormInterface::class, $this->factory->create(static::TESTED_TYPE, new \DateTime())); + $this->assertInstanceOf(FormInterface::class, $this->factory->create(static::TESTED_TYPE, new \DateTime(), ['widget' => 'choice'])); } public function testSingleTextWidgetShouldUseTheRightInputType() @@ -777,6 +795,7 @@ public function testPassDefaultPlaceholderToViewIfNotRequired() { $view = $this->factory->create(static::TESTED_TYPE, null, [ 'required' => false, + 'widget' => 'choice', ]) ->createView(); @@ -789,6 +808,7 @@ public function testPassNoPlaceholderToViewIfRequired() { $view = $this->factory->create(static::TESTED_TYPE, null, [ 'required' => true, + 'widget' => 'choice', ]) ->createView(); @@ -800,6 +820,7 @@ public function testPassNoPlaceholderToViewIfRequired() public function testPassPlaceholderAsString() { $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', 'placeholder' => 'Empty', ]) ->createView(); @@ -812,6 +833,7 @@ public function testPassPlaceholderAsString() public function testPassPlaceholderAsArray() { $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', 'placeholder' => [ 'year' => 'Empty year', 'month' => 'Empty month', @@ -828,6 +850,7 @@ public function testPassPlaceholderAsArray() public function testPassPlaceholderAsPartialArrayAddEmptyIfNotRequired() { $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', 'required' => false, 'placeholder' => [ 'year' => 'Empty year', @@ -844,6 +867,7 @@ public function testPassPlaceholderAsPartialArrayAddEmptyIfNotRequired() public function testPassPlaceholderAsPartialArrayAddNullIfRequired() { $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', 'required' => true, 'placeholder' => [ 'year' => 'Empty year', @@ -956,6 +980,7 @@ public function testDayErrorsBubbleUp($widget) public function testYears() { $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', 'years' => [1900, 2000, 2040], ]) ->createView(); @@ -970,7 +995,7 @@ public function testYears() public function testPassDefaultChoiceTranslationDomain() { - $form = $this->factory->create(static::TESTED_TYPE); + $form = $this->factory->create(static::TESTED_TYPE, null, ['widget' => 'choice']); $view = $form->createView(); $this->assertFalse($view['year']->vars['choice_translation_domain']); @@ -981,6 +1006,7 @@ public function testPassDefaultChoiceTranslationDomain() public function testPassChoiceTranslationDomainAsString() { $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', 'choice_translation_domain' => 'messages', ]); @@ -993,6 +1019,7 @@ public function testPassChoiceTranslationDomainAsString() public function testPassChoiceTranslationDomainAsArray() { $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', 'choice_translation_domain' => [ 'year' => 'foo', 'day' => 'test', @@ -1025,6 +1052,7 @@ public function testSubmitNullWithSingleText() public function testSubmitNullUsesDefaultEmptyData($emptyData = [], $expectedData = null) { $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', 'empty_data' => $emptyData, ]); $form->submit(null); @@ -1057,9 +1085,7 @@ public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedDa public static function provideEmptyData() { $expectedData = \DateTime::createFromFormat('Y-m-d H:i:s', '2018-11-11 00:00:00'); - $lazyEmptyData = static function (FormInterface $form) { - return $form->getConfig()->getCompound() ? ['year' => '2018', 'month' => '11', 'day' => '11'] : '2018-11-11'; - }; + $lazyEmptyData = static fn (FormInterface $form) => $form->getConfig()->getCompound() ? ['year' => '2018', 'month' => '11', 'day' => '11'] : '2018-11-11'; return [ 'Simple field' => ['single_text', '2018-11-11', $expectedData], @@ -1085,4 +1111,9 @@ public function testSubmitStringWithCustomInputFormat() $this->assertSame('14/01/2018', $form->getData()); } + + protected function getTestOptions(): array + { + return ['widget' => 'choice']; + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php index 1d2ff4ff12003..be89c559f6d3c 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php @@ -439,9 +439,8 @@ public function testSubformCallsSettersIfReferenceIsScalar() $builder->add('referenceCopy', static::TESTED_TYPE); $builder->get('referenceCopy')->addViewTransformer(new CallbackTransformer( function () {}, - function ($value) { // reverseTransform - return 'foobar'; - } + fn ($value) => // reverseTransform +'foobar' )); $form = $builder->getForm(); @@ -464,9 +463,8 @@ public function testSubformAlwaysInsertsIntoArrays() $builder->add('referenceCopy', static::TESTED_TYPE); $builder->get('referenceCopy')->addViewTransformer(new CallbackTransformer( function () {}, - function ($value) use ($ref2) { // reverseTransform - return $ref2; - } + fn ($value) => // reverseTransform +$ref2 )); $form = $builder->getForm(); @@ -885,14 +883,14 @@ public function getCurrency() class MoneyDataMapper implements DataMapperInterface { - public function mapDataToForms($data, $forms) + public function mapDataToForms(mixed $viewData, \Traversable $forms): void { $forms = iterator_to_array($forms); - $forms['amount']->setData($data ? $data->getAmount() : 0); - $forms['currency']->setData($data ? $data->getCurrency() : 'EUR'); + $forms['amount']->setData($viewData ? $viewData->getAmount() : 0); + $forms['currency']->setData($viewData ? $viewData->getCurrency() : 'EUR'); } - public function mapFormsToData($forms, &$data) + public function mapFormsToData(\Traversable $forms, mixed &$viewData): void { $forms = iterator_to_array($forms); @@ -904,7 +902,7 @@ public function mapFormsToData($forms, &$data) throw $failure; } - $data = new Money( + $viewData = new Money( $forms['amount']->getData(), $forms['currency']->getData() ); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php index 9b3bbbfd00ee0..4ed930bbd5381 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php @@ -28,6 +28,7 @@ public function testSubmitDateTime() 'model_timezone' => 'UTC', 'view_timezone' => 'UTC', 'input' => 'datetime', + 'widget' => 'choice', ]); $input = [ @@ -49,6 +50,7 @@ public function testSubmitDateTimeImmutable() 'model_timezone' => 'UTC', 'view_timezone' => 'UTC', 'input' => 'datetime_immutable', + 'widget' => 'choice', ]); $input = [ @@ -71,6 +73,7 @@ public function testSubmitString() 'model_timezone' => 'UTC', 'view_timezone' => 'UTC', 'input' => 'string', + 'widget' => 'choice', ]); $input = [ @@ -106,6 +109,7 @@ public function testSubmitTimestamp() 'model_timezone' => 'UTC', 'view_timezone' => 'UTC', 'input' => 'timestamp', + 'widget' => 'choice', ]); $input = [ @@ -127,6 +131,7 @@ public function testSubmitArray() 'model_timezone' => 'UTC', 'view_timezone' => 'UTC', 'input' => 'array', + 'widget' => 'choice', ]); $input = [ @@ -288,6 +293,7 @@ public function testPreSetDataDifferentTimezones() 'input' => 'datetime', 'with_seconds' => true, 'reference_date' => new \DateTimeImmutable('2019-01-01', new \DateTimeZone('UTC')), + 'widget' => 'choice', ]); $form->setData(new \DateTime('2022-01-01 15:09:10', new \DateTimeZone('UTC'))); @@ -307,6 +313,7 @@ public function testPreSetDataDifferentTimezonesDuringDaylightSavingTime() 'input' => 'datetime', 'with_seconds' => true, 'reference_date' => new \DateTimeImmutable('2019-07-12', new \DateTimeZone('UTC')), + 'widget' => 'choice', ]); $form->setData(new \DateTime('2022-04-29 15:09:10', new \DateTimeZone('UTC'))); @@ -358,6 +365,7 @@ public function testSubmitDifferentTimezones() 'input' => 'datetime', 'with_seconds' => true, 'reference_date' => new \DateTimeImmutable('2019-01-01', new \DateTimeZone('UTC')), + 'widget' => 'choice', ]); $form->submit([ 'hour' => '16', @@ -376,6 +384,7 @@ public function testSubmitDifferentTimezonesDuringDaylightSavingTime() 'input' => 'datetime', 'with_seconds' => true, 'reference_date' => new \DateTimeImmutable('2019-07-12', new \DateTimeZone('UTC')), + 'widget' => 'choice', ]); $form->submit([ 'hour' => '16', @@ -456,6 +465,7 @@ public function testSetDataWithoutMinutes() 'view_timezone' => 'UTC', 'input' => 'datetime', 'with_minutes' => false, + 'widget' => 'choice', ]); $form->setData(new \DateTime('03:04:05 UTC')); @@ -470,6 +480,7 @@ public function testSetDataWithSeconds() 'view_timezone' => 'UTC', 'input' => 'datetime', 'with_seconds' => true, + 'widget' => 'choice', ]); $form->setData(new \DateTime('03:04:05 UTC')); @@ -485,6 +496,7 @@ public function testSetDataDifferentTimezones() 'input' => 'string', 'with_seconds' => true, 'reference_date' => new \DateTimeImmutable('2013-01-01 00:00:00', new \DateTimeZone('America/New_York')), + 'widget' => 'choice', ]); $dateTime = new \DateTime('2013-01-01 12:04:05'); @@ -512,6 +524,7 @@ public function testSetDataDifferentTimezonesDateTime() 'input' => 'datetime', 'with_seconds' => true, 'reference_date' => new \DateTimeImmutable('now', new \DateTimeZone('America/New_York')), + 'widget' => 'choice', ]); $dateTime = new \DateTime('12:04:05'); @@ -540,6 +553,7 @@ public function testSetDataDifferentTimezonesDuringDaylightSavingTime() 'input' => 'datetime', 'with_seconds' => true, 'reference_date' => new \DateTimeImmutable('2019-07-12', new \DateTimeZone('UTC')), + 'widget' => 'choice', ]); $form->setData(new \DateTime('2019-07-24 14:09:10', new \DateTimeZone('UTC'))); @@ -557,6 +571,7 @@ public function testSetDataDifferentTimezonesWithoutReferenceDate() 'view_timezone' => 'Europe/Berlin', 'input' => 'datetime', 'with_seconds' => true, + 'widget' => 'choice', ]); $form->setData(new \DateTime('2019-07-24 14:09:10', new \DateTimeZone('UTC'))); @@ -568,6 +583,7 @@ public function testHoursOption() { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'hours' => [6, 7], + 'widget' => 'choice', ]); $view = $form->createView(); @@ -582,6 +598,7 @@ public function testIsMinuteWithinRangeReturnsTrueIfWithin() { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'minutes' => [6, 7], + 'widget' => 'choice', ]); $view = $form->createView(); @@ -597,6 +614,7 @@ public function testIsSecondWithinRangeReturnsTrueIfWithin() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'seconds' => [6, 7], 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -733,7 +751,7 @@ public function testInitializeWithDateTime() { // Throws an exception if "data_class" option is not explicitly set // to null in the type - $this->assertInstanceOf(FormInterface::class, $this->factory->create(static::TESTED_TYPE, new \DateTime())); + $this->assertInstanceOf(FormInterface::class, $this->factory->create(static::TESTED_TYPE, new \DateTime(), ['widget' => 'choice'])); } public function testSingleTextWidgetShouldUseTheRightInputType() @@ -789,6 +807,7 @@ public function testPassDefaultPlaceholderToViewIfNotRequired() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'required' => false, 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -802,6 +821,7 @@ public function testPassNoPlaceholderToViewIfRequired() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'required' => true, 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -815,6 +835,7 @@ public function testPassPlaceholderAsString() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'placeholder' => 'Empty', 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -832,6 +853,7 @@ public function testPassPlaceholderAsArray() 'second' => 'Empty second', ], 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -849,6 +871,7 @@ public function testPassPlaceholderAsPartialArrayAddEmptyIfNotRequired() 'second' => 'Empty second', ], 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -866,6 +889,7 @@ public function testPassPlaceholderAsPartialArrayAddNullIfRequired() 'second' => 'Empty second', ], 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -934,6 +958,7 @@ public function testInitializeWithSecondsAndWithoutMinutes() $this->factory->create(static::TESTED_TYPE, null, [ 'with_minutes' => false, 'with_seconds' => true, + 'widget' => 'choice', ]); } @@ -942,6 +967,7 @@ public function testThrowExceptionIfHoursIsInvalid() $this->expectException(InvalidOptionsException::class); $this->factory->create(static::TESTED_TYPE, null, [ 'hours' => 'bad value', + 'widget' => 'choice', ]); } @@ -950,6 +976,7 @@ public function testThrowExceptionIfMinutesIsInvalid() $this->expectException(InvalidOptionsException::class); $this->factory->create(static::TESTED_TYPE, null, [ 'minutes' => 'bad value', + 'widget' => 'choice', ]); } @@ -958,6 +985,7 @@ public function testThrowExceptionIfSecondsIsInvalid() $this->expectException(InvalidOptionsException::class); $this->factory->create(static::TESTED_TYPE, null, [ 'seconds' => 'bad value', + 'widget' => 'choice', ]); } @@ -968,6 +996,7 @@ public function testReferenceDateTimezoneMustMatchModelTimezone() 'model_timezone' => 'UTC', 'view_timezone' => 'Europe/Berlin', 'reference_date' => new \DateTimeImmutable('now', new \DateTimeZone('Europe/Berlin')), + 'widget' => 'choice', ]); } @@ -976,6 +1005,7 @@ public function testModelTimezoneDefaultToReferenceDateTimezoneIfProvided() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'view_timezone' => 'Europe/Berlin', 'reference_date' => new \DateTimeImmutable('now', new \DateTimeZone('Europe/Berlin')), + 'widget' => 'choice', ]); $this->assertSame('Europe/Berlin', $form->getConfig()->getOption('model_timezone')); @@ -985,6 +1015,7 @@ public function testViewTimezoneDefaultsToModelTimezoneIfProvided() { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'model_timezone' => 'Europe/Berlin', + 'widget' => 'choice', ]); $this->assertSame('Europe/Berlin', $form->getConfig()->getOption('view_timezone')); @@ -992,7 +1023,7 @@ public function testViewTimezoneDefaultsToModelTimezoneIfProvided() public function testPassDefaultChoiceTranslationDomain() { - $form = $this->factory->create(static::TESTED_TYPE); + $form = $this->factory->create(static::TESTED_TYPE, null, ['widget' => 'choice']); $view = $form->createView(); $this->assertFalse($view['hour']->vars['choice_translation_domain']); @@ -1004,6 +1035,7 @@ public function testPassChoiceTranslationDomainAsString() $form = $this->factory->create(static::TESTED_TYPE, null, [ 'choice_translation_domain' => 'messages', 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -1020,6 +1052,7 @@ public function testPassChoiceTranslationDomainAsArray() 'second' => 'test', ], 'with_seconds' => true, + 'widget' => 'choice', ]); $view = $form->createView(); @@ -1039,6 +1072,7 @@ public function testSubmitNullUsesDefaultEmptyData($emptyData = [], $expectedDat { $form = $this->factory->create(static::TESTED_TYPE, null, [ 'empty_data' => $emptyData, + 'widget' => 'choice', ]); $form->submit(null); @@ -1055,6 +1089,7 @@ public function testArrayTimeWithReferenceDoesNotUseReferenceTimeOnZero() 'view_timezone' => 'Europe/Berlin', 'reference_date' => new \DateTimeImmutable('01-01-2021 12:34:56', new \DateTimeZone('UTC')), 'input' => 'array', + 'widget' => 'choice', ]); $input = [ @@ -1082,6 +1117,7 @@ public function testArrayTimeWithReferenceDoesUseReferenceDateOnModelTransform() 'view_timezone' => 'Europe/Berlin', 'reference_date' => new \DateTimeImmutable('01-05-2021 12:34:56', new \DateTimeZone('UTC')), 'input' => 'array', + 'widget' => 'choice', ]); $this->assertSame($input, $form->getData()); @@ -1113,9 +1149,7 @@ public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedDa public static function provideEmptyData() { $expectedData = \DateTime::createFromFormat('Y-m-d H:i', '1970-01-01 21:23'); - $lazyEmptyData = static function (FormInterface $form) { - return $form->getConfig()->getCompound() ? ['hour' => '21', 'minute' => '23'] : '21:23'; - }; + $lazyEmptyData = static fn (FormInterface $form) => $form->getConfig()->getCompound() ? ['hour' => '21', 'minute' => '23'] : '21:23'; return [ 'Simple field' => ['single_text', '21:23', $expectedData], @@ -1126,4 +1160,9 @@ public static function provideEmptyData() 'Compound choice field lazy' => ['choice', $lazyEmptyData, $expectedData], ]; } + + protected function getTestOptions(): array + { + return ['widget' => 'choice']; + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php index 7fcc80fee7de5..81418527eefe9 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php @@ -23,7 +23,7 @@ class FormTypeCsrfExtensionTest_ChildType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { // The form needs a child in order to trigger CSRF protection by // default diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php index 39009b598c530..fd9870fa6d50e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php @@ -335,9 +335,7 @@ public function testSerializeWithFormAddedMultipleTimes() $form1View = new FormView(); $form2View = new FormView(); $child1View = new FormView(); - $child1View->vars['is_selected'] = function ($choice, array $values) { - return \in_array($choice, $values, true); - }; + $child1View->vars['is_selected'] = fn ($choice, array $values) => \in_array($choice, $values, true); $form1->add($child1); $form2->add($child1); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php index f712805e004e0..e1698e6b9b769 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php @@ -486,7 +486,7 @@ class Foo public $bar; public $baz; - public static function loadValidatorMetadata(ClassMetadata $metadata) + public static function loadValidatorMetadata(ClassMetadata $metadata): void { $metadata->addPropertyConstraint('bar', new NotBlank()); } @@ -494,7 +494,7 @@ public static function loadValidatorMetadata(ClassMetadata $metadata) class FooType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('bar') @@ -504,7 +504,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) ; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('data_class', Foo::class); } @@ -516,7 +516,7 @@ class Review public $title; public $author; - public static function loadValidatorMetadata(ClassMetadata $metadata) + public static function loadValidatorMetadata(ClassMetadata $metadata): void { $metadata->addPropertyConstraint('title', new NotBlank()); $metadata->addPropertyConstraint('rating', new NotBlank()); @@ -525,7 +525,7 @@ public static function loadValidatorMetadata(ClassMetadata $metadata) class ReviewType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('rating', IntegerType::class, [ @@ -538,7 +538,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) ; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('data_class', Review::class); } @@ -548,7 +548,7 @@ class Customer { public $email; - public static function loadValidatorMetadata(ClassMetadata $metadata) + public static function loadValidatorMetadata(ClassMetadata $metadata): void { $metadata->addPropertyConstraint('email', new NotBlank()); } @@ -556,14 +556,14 @@ public static function loadValidatorMetadata(ClassMetadata $metadata) class CustomerType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('email') ; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('data_class', Customer::class); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index c614a1ac181f4..7d9061c882808 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -272,7 +272,7 @@ public function testDontValidateIfNotSynchronized() ]) ->setData($object) ->addViewTransformer(new CallbackTransformer( - function ($data) { return $data; }, + fn ($data) => $data, function () { throw new TransformationFailedException(); } )) ->getForm(); @@ -309,7 +309,7 @@ public function testAddInvalidErrorEvenIfNoValidationGroups() ]) ->setData($object) ->addViewTransformer(new CallbackTransformer( - function ($data) { return $data; }, + fn ($data) => $data, function () { throw new TransformationFailedException(); } )) ->getForm(); @@ -344,7 +344,7 @@ public function testDontValidateConstraintsIfNotSynchronized() $form = $this->getBuilder('name', '\stdClass', $options) ->setData($object) ->addViewTransformer(new CallbackTransformer( - function ($data) { return $data; }, + fn ($data) => $data, function () { throw new TransformationFailedException(); } )) ->getForm(); @@ -375,7 +375,7 @@ public function testTransformationFailedExceptionInvalidMessageIsUsed() ]) ->setData($object) ->addViewTransformer(new CallbackTransformer( - function ($data) { return $data; }, + fn ($data) => $data, function () { $failure = new TransformationFailedException(); $failure->setInvalidMessage('safe message to be used', ['{{ bar }}' => 'bar']); @@ -451,9 +451,7 @@ public function testDontExecuteFunctionNames() public function testHandleClosureValidationGroups() { $object = new \stdClass(); - $options = ['validation_groups' => function (FormInterface $form) { - return ['group1', 'group2']; - }]; + $options = ['validation_groups' => fn (FormInterface $form) => ['group1', 'group2']]; $form = $this->getCompoundForm($object, $options); $form->submit([]); @@ -565,9 +563,7 @@ public function testUseInheritedClosureValidationGroup() $object = new \stdClass(); $parentOptions = [ - 'validation_groups' => function () { - return ['group1', 'group2']; - }, + 'validation_groups' => fn () => ['group1', 'group2'], ]; $parent = $this->getBuilder('parent', null, $parentOptions) ->setCompound(true) diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/BirthdayTypeValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/BirthdayTypeValidatorExtensionTest.php index 7ef39bde14d13..9fb1e2421926d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/BirthdayTypeValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/BirthdayTypeValidatorExtensionTest.php @@ -20,7 +20,7 @@ class BirthdayTypeValidatorExtensionTest extends BaseValidatorExtensionTestCase protected function createForm(array $options = []) { - return $this->factory->create(BirthdayType::class, null, $options); + return $this->factory->create(BirthdayType::class, null, $options + ['widget' => 'choice']); } public function testInvalidMessage() diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/DateTimeTypeValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/DateTimeTypeValidatorExtensionTest.php index a8206e40c322b..df601ab8702b4 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/DateTimeTypeValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/DateTimeTypeValidatorExtensionTest.php @@ -20,7 +20,7 @@ class DateTimeTypeValidatorExtensionTest extends BaseValidatorExtensionTestCase protected function createForm(array $options = []) { - return $this->factory->create(DateTimeType::class, null, $options); + return $this->factory->create(DateTimeType::class, null, $options + ['widget' => 'choice']); } public function testInvalidMessage() diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/DateTypeValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/DateTypeValidatorExtensionTest.php index 32594430dc5d8..163fe91fd0bb4 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/DateTypeValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/DateTypeValidatorExtensionTest.php @@ -20,7 +20,7 @@ class DateTypeValidatorExtensionTest extends BaseValidatorExtensionTestCase protected function createForm(array $options = []) { - return $this->factory->create(DateType::class, null, $options); + return $this->factory->create(DateType::class, null, $options + ['widget' => 'choice']); } public function testInvalidMessage() diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/TimeTypeValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/TimeTypeValidatorExtensionTest.php index 09893b98a1e1e..ab85366e17e13 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/TimeTypeValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/TimeTypeValidatorExtensionTest.php @@ -20,7 +20,7 @@ class TimeTypeValidatorExtensionTest extends BaseValidatorExtensionTestCase protected function createForm(array $options = []) { - return $this->factory->create(TimeType::class, null, $options); + return $this->factory->create(TimeType::class, null, $options + ['widget' => 'choice']); } public function testInvalidMessage() diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php index 34c911f52a9fa..96c39c5cae524 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php @@ -26,11 +26,7 @@ public function testPostMaxSizeTranslation() $resolver = new OptionsResolver(); $resolver->setDefault('post_max_size_message', 'old max {{ max }}!'); - $resolver->setDefault('upload_max_size_message', function (Options $options) { - return function () use ($options) { - return $options['post_max_size_message']; - }; - }); + $resolver->setDefault('upload_max_size_message', fn (Options $options) => fn () => $options['post_max_size_message']); $extension->configureOptions($resolver); $options = $resolver->resolve(); @@ -46,7 +42,7 @@ public function trans($id, array $parameters = [], $domain = null, $locale = nul return 'translated max {{ max }}!'; } - public function setLocale($locale) + public function setLocale($locale): void { } diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php index 5a9658a74cc2b..b2c48305552c9 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php @@ -91,7 +91,7 @@ protected function getForm($name = 'name', $propertyPath = null, $dataClass = nu if (!$synchronized) { $config->addViewTransformer(new CallbackTransformer( - function ($normData) { return $normData; }, + fn ($normData) => $normData, function () { throw new TransformationFailedException(); } )); } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/AlternatingRowType.php b/src/Symfony/Component/Form/Tests/Fixtures/AlternatingRowType.php index 556166f5547ed..3e0bb40c2b172 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/AlternatingRowType.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/AlternatingRowType.php @@ -9,7 +9,7 @@ class AlternatingRowType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { $form = $event->getForm(); diff --git a/src/Symfony/Component/Form/Tests/Fixtures/ArrayChoiceLoader.php b/src/Symfony/Component/Form/Tests/Fixtures/ArrayChoiceLoader.php index 7224d0e93639e..8c5a3a4733e6c 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/ArrayChoiceLoader.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/ArrayChoiceLoader.php @@ -8,8 +8,6 @@ class ArrayChoiceLoader extends CallbackChoiceLoader { public function __construct(array $choices = []) { - parent::__construct(static function () use ($choices): array { - return $choices; - }); + parent::__construct(static fn (): array => $choices); } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/AuthorType.php b/src/Symfony/Component/Form/Tests/Fixtures/AuthorType.php index 84c988984f64d..a55dbfeaccea2 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/AuthorType.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/AuthorType.php @@ -8,7 +8,7 @@ class AuthorType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('firstName') @@ -16,7 +16,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) ; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', diff --git a/src/Symfony/Component/Form/Tests/Fixtures/BlockPrefixedFooTextType.php b/src/Symfony/Component/Form/Tests/Fixtures/BlockPrefixedFooTextType.php index 3fda7a55dd14d..fa145a49c27e2 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/BlockPrefixedFooTextType.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/BlockPrefixedFooTextType.php @@ -16,7 +16,7 @@ class BlockPrefixedFooTextType extends AbstractType { - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('block_prefix', 'foo'); } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/ChoiceSubType.php b/src/Symfony/Component/Form/Tests/Fixtures/ChoiceSubType.php index d67f0b96ace50..38b22b87e5085 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/ChoiceSubType.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/ChoiceSubType.php @@ -20,15 +20,13 @@ */ class ChoiceSubType extends AbstractType { - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults(['expanded' => true]); - $resolver->setNormalizer('choices', function () { - return [ - 'attr1' => 'Attribute 1', - 'attr2' => 'Attribute 2', - ]; - }); + $resolver->setNormalizer('choices', fn () => [ + 'attr1' => 'Attribute 1', + 'attr2' => 'Attribute 2', + ]); } public function getParent(): ?string diff --git a/src/Symfony/Component/Form/Tests/Fixtures/ChoiceTypeExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/ChoiceTypeExtension.php index 1751531d19adc..3549763f16646 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/ChoiceTypeExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/ChoiceTypeExtension.php @@ -18,7 +18,7 @@ class ChoiceTypeExtension extends AbstractTypeExtension { public static $extendedType; - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('choices', [ 'A' => 'a', diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/default_option_with_normalizer.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/default_option_with_normalizer.txt index 4a226f576d60f..0f6f1f40e728e 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/default_option_with_normalizer.txt +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/default_option_with_normalizer.txt @@ -20,7 +20,6 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (choice_translation_domain Normalizers [ %s Closure(%s class:%s - this: %s file: %s line: %s } %s diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json index c9f453c8d6cc6..3a9b7a7ecce4d 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json @@ -16,6 +16,7 @@ "group_by", "multiple", "placeholder", + "placeholder_attr", "preferred_choices" ], "overridden": { diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt index c4c4e901804af..a15ac42dae0f7 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt @@ -18,8 +18,8 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice") group_by data multiple disabled placeholder form_attr - preferred_choices getter - help + placeholder_attr getter + preferred_choices help help_attr help_html help_translation_parameters diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php index 70c710e922fdf..477f36148e7b6 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php @@ -16,12 +16,12 @@ class FooTypeBarExtension extends AbstractTypeExtension { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->setAttribute('bar', 'x'); } - public function getAllowedOptionValues() + public function getAllowedOptionValues(): array { return [ 'a_or_b' => ['c'], diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php index a11f158844732..9720439eb1515 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php @@ -16,7 +16,7 @@ class FooTypeBazExtension extends AbstractTypeExtension { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->setAttribute('baz', 'x'); } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php index b04eb61721c41..0c7e29414443e 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/LazyChoiceTypeExtension.php @@ -19,14 +19,12 @@ class LazyChoiceTypeExtension extends AbstractTypeExtension { public static $extendedType; - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { - $resolver->setDefault('choice_loader', ChoiceList::lazy($this, function () { - return [ - 'Lazy A' => 'lazy_a', - 'Lazy B' => 'lazy_b', - ]; - })); + $resolver->setDefault('choice_loader', ChoiceList::lazy($this, fn () => [ + 'Lazy A' => 'lazy_a', + 'Lazy B' => 'lazy_b', + ])); } public static function getExtendedTypes(): iterable diff --git a/src/Symfony/Component/Form/Tests/Fixtures/NotMappedType.php b/src/Symfony/Component/Form/Tests/Fixtures/NotMappedType.php index 14c340b8917af..3d55122e795ae 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/NotMappedType.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/NotMappedType.php @@ -16,7 +16,7 @@ class NotMappedType extends AbstractType { - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('mapped', false); } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php index 3f7c4005fe112..5f6556ca6ec6f 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/TestExtension.php @@ -31,7 +31,7 @@ public function __construct(FormTypeGuesserInterface $guesser) public function addType(FormTypeInterface $type) { - $this->types[\get_class($type)] = $type; + $this->types[$type::class] = $type; } public function getType($name): FormTypeInterface diff --git a/src/Symfony/Component/Form/Tests/FormFactoryBuilderTest.php b/src/Symfony/Component/Form/Tests/FormFactoryBuilderTest.php index fd6ef52bd5f78..818ab1b2b12b0 100644 --- a/src/Symfony/Component/Form/Tests/FormFactoryBuilderTest.php +++ b/src/Symfony/Component/Form/Tests/FormFactoryBuilderTest.php @@ -40,7 +40,7 @@ public function testAddType() $extensions = $registry->getExtensions(); $this->assertCount(1, $extensions); - $this->assertTrue($extensions[0]->hasType(\get_class($this->type))); + $this->assertTrue($extensions[0]->hasType($this->type::class)); $this->assertNull($extensions[0]->getTypeGuesser()); } diff --git a/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php b/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php index d8eeff44899b1..cd3b13adadc56 100644 --- a/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php +++ b/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php @@ -47,7 +47,7 @@ public function testTranslationFileIsValidWithoutEntityLoader($filePath) public static function provideTranslationFiles() { return array_map( - function ($filePath) { return (array) $filePath; }, + fn ($filePath) => (array) $filePath, glob(\dirname(__DIR__, 2).'/Resources/translations/*.xlf') ); } diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 0f9556b1a572e..1b7af764ad2e2 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -1112,12 +1112,12 @@ public function testIsEmptyCallback() { $config = new FormConfigBuilder('foo', null, new EventDispatcher()); - $config->setIsEmptyCallback(function ($modelData): bool { return 'ccc' === $modelData; }); + $config->setIsEmptyCallback(fn ($modelData): bool => 'ccc' === $modelData); $form = new Form($config); $form->setData('ccc'); $this->assertTrue($form->isEmpty()); - $config->setIsEmptyCallback(function (): bool { return false; }); + $config->setIsEmptyCallback(fn (): bool => false); $form = new Form($config); $form->setData(null); $this->assertFalse($form->isEmpty()); diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index ccf25ad5631ed..1401929459a35 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -17,14 +17,14 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/options-resolver": "^5.4|^6.0", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-icu": "^1.21", "symfony/polyfill-mbstring": "~1.0", "symfony/property-access": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "require-dev": { "doctrine/collections": "^1.0|^2.0", @@ -52,14 +52,8 @@ "symfony/framework-bundle": "<5.4", "symfony/http-kernel": "<5.4", "symfony/translation": "<5.4", - "symfony/translation-contracts": "<1.1.7", - "symfony/twig-bridge": "<5.4.21|>=6,<6.2.7" - }, - "suggest": { - "symfony/validator": "For form validation.", - "symfony/security-core": "For hashing users passwords.", - "symfony/security-csrf": "For protecting forms against CSRF attacks.", - "symfony/twig-bridge": "For templating with Twig." + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Form\\": "" }, diff --git a/src/Symfony/Component/HtmlSanitizer/HtmlSanitizer.php b/src/Symfony/Component/HtmlSanitizer/HtmlSanitizer.php index 2f4947256602e..fb668921a8643 100644 --- a/src/Symfony/Component/HtmlSanitizer/HtmlSanitizer.php +++ b/src/Symfony/Component/HtmlSanitizer/HtmlSanitizer.php @@ -19,8 +19,6 @@ /** * @author Titouan Galopin - * - * @experimental */ final class HtmlSanitizer implements HtmlSanitizerInterface { diff --git a/src/Symfony/Component/HtmlSanitizer/HtmlSanitizerConfig.php b/src/Symfony/Component/HtmlSanitizer/HtmlSanitizerConfig.php index 34576ca9d95cf..aba306748b7cf 100644 --- a/src/Symfony/Component/HtmlSanitizer/HtmlSanitizerConfig.php +++ b/src/Symfony/Component/HtmlSanitizer/HtmlSanitizerConfig.php @@ -16,8 +16,6 @@ /** * @author Titouan Galopin - * - * @experimental */ class HtmlSanitizerConfig { diff --git a/src/Symfony/Component/HtmlSanitizer/HtmlSanitizerInterface.php b/src/Symfony/Component/HtmlSanitizer/HtmlSanitizerInterface.php index 559bcb6a46a98..f68d9944f51e8 100644 --- a/src/Symfony/Component/HtmlSanitizer/HtmlSanitizerInterface.php +++ b/src/Symfony/Component/HtmlSanitizer/HtmlSanitizerInterface.php @@ -18,8 +18,6 @@ * ({@see https://wicg.github.io/sanitizer-api/}). * * @author Titouan Galopin - * - * @experimental */ interface HtmlSanitizerInterface { diff --git a/src/Symfony/Component/HtmlSanitizer/Parser/MastermindsParser.php b/src/Symfony/Component/HtmlSanitizer/Parser/MastermindsParser.php index f9752fc04901f..b49849bd23d27 100644 --- a/src/Symfony/Component/HtmlSanitizer/Parser/MastermindsParser.php +++ b/src/Symfony/Component/HtmlSanitizer/Parser/MastermindsParser.php @@ -15,8 +15,6 @@ /** * @author Titouan Galopin - * - * @experimental */ final class MastermindsParser implements ParserInterface { diff --git a/src/Symfony/Component/HtmlSanitizer/Parser/ParserInterface.php b/src/Symfony/Component/HtmlSanitizer/Parser/ParserInterface.php index 50d56fad6d3be..1a12b450c6d3c 100644 --- a/src/Symfony/Component/HtmlSanitizer/Parser/ParserInterface.php +++ b/src/Symfony/Component/HtmlSanitizer/Parser/ParserInterface.php @@ -15,8 +15,6 @@ * Transforms an untrusted HTML input string into a DOM tree. * * @author Titouan Galopin - * - * @experimental */ interface ParserInterface { diff --git a/src/Symfony/Component/HtmlSanitizer/Visitor/AttributeSanitizer/AttributeSanitizerInterface.php b/src/Symfony/Component/HtmlSanitizer/Visitor/AttributeSanitizer/AttributeSanitizerInterface.php index c4daa1d17fbe3..92e983e87f1b5 100644 --- a/src/Symfony/Component/HtmlSanitizer/Visitor/AttributeSanitizer/AttributeSanitizerInterface.php +++ b/src/Symfony/Component/HtmlSanitizer/Visitor/AttributeSanitizer/AttributeSanitizerInterface.php @@ -17,8 +17,6 @@ * Implements attribute-specific sanitization logic. * * @author Titouan Galopin - * - * @experimental */ interface AttributeSanitizerInterface { diff --git a/src/Symfony/Component/HtmlSanitizer/Visitor/AttributeSanitizer/UrlAttributeSanitizer.php b/src/Symfony/Component/HtmlSanitizer/Visitor/AttributeSanitizer/UrlAttributeSanitizer.php index 2d5c5f0b975db..a3dbf2f098663 100644 --- a/src/Symfony/Component/HtmlSanitizer/Visitor/AttributeSanitizer/UrlAttributeSanitizer.php +++ b/src/Symfony/Component/HtmlSanitizer/Visitor/AttributeSanitizer/UrlAttributeSanitizer.php @@ -15,7 +15,7 @@ use Symfony\Component\HtmlSanitizer\TextSanitizer\UrlSanitizer; /** - * @experimental + * @author Titouan Galopin */ final class UrlAttributeSanitizer implements AttributeSanitizerInterface { diff --git a/src/Symfony/Component/HtmlSanitizer/Visitor/Node/BlockedNode.php b/src/Symfony/Component/HtmlSanitizer/Visitor/Node/BlockedNode.php index d438313d4ec76..8376f6664873f 100644 --- a/src/Symfony/Component/HtmlSanitizer/Visitor/Node/BlockedNode.php +++ b/src/Symfony/Component/HtmlSanitizer/Visitor/Node/BlockedNode.php @@ -13,8 +13,6 @@ /** * @author Titouan Galopin - * - * @experimental */ final class BlockedNode implements NodeInterface { diff --git a/src/Symfony/Component/HtmlSanitizer/Visitor/Node/DocumentNode.php b/src/Symfony/Component/HtmlSanitizer/Visitor/Node/DocumentNode.php index d5ef5363015e7..17037f84da7c1 100644 --- a/src/Symfony/Component/HtmlSanitizer/Visitor/Node/DocumentNode.php +++ b/src/Symfony/Component/HtmlSanitizer/Visitor/Node/DocumentNode.php @@ -13,8 +13,6 @@ /** * @author Titouan Galopin - * - * @experimental */ final class DocumentNode implements NodeInterface { diff --git a/src/Symfony/Component/HtmlSanitizer/Visitor/Node/Node.php b/src/Symfony/Component/HtmlSanitizer/Visitor/Node/Node.php index 8a4e5c32aa7ac..46a6b17a443de 100644 --- a/src/Symfony/Component/HtmlSanitizer/Visitor/Node/Node.php +++ b/src/Symfony/Component/HtmlSanitizer/Visitor/Node/Node.php @@ -15,8 +15,6 @@ /** * @author Titouan Galopin - * - * @experimental */ final class Node implements NodeInterface { diff --git a/src/Symfony/Component/HtmlSanitizer/Visitor/Node/NodeInterface.php b/src/Symfony/Component/HtmlSanitizer/Visitor/Node/NodeInterface.php index 27d9da7ed97ac..53c69c0875096 100644 --- a/src/Symfony/Component/HtmlSanitizer/Visitor/Node/NodeInterface.php +++ b/src/Symfony/Component/HtmlSanitizer/Visitor/Node/NodeInterface.php @@ -17,8 +17,6 @@ * Once the sanitization is done, nodes are rendered into the final output string. * * @author Titouan Galopin - * - * @experimental */ interface NodeInterface { diff --git a/src/Symfony/Component/HtmlSanitizer/Visitor/Node/TextNode.php b/src/Symfony/Component/HtmlSanitizer/Visitor/Node/TextNode.php index f06b7ccdf47d1..d4378adc19d5b 100644 --- a/src/Symfony/Component/HtmlSanitizer/Visitor/Node/TextNode.php +++ b/src/Symfony/Component/HtmlSanitizer/Visitor/Node/TextNode.php @@ -15,8 +15,6 @@ /** * @author Titouan Galopin - * - * @experimental */ final class TextNode implements NodeInterface { diff --git a/src/Symfony/Component/HttpClient/AmpHttpClient.php b/src/Symfony/Component/HttpClient/AmpHttpClient.php index a8d731150b4a5..94d8fa414ee45 100644 --- a/src/Symfony/Component/HttpClient/AmpHttpClient.php +++ b/src/Symfony/Component/HttpClient/AmpHttpClient.php @@ -98,7 +98,7 @@ public function request(string $method, string $url, array $options = []): Respo } if (!isset($options['normalized_headers']['user-agent'])) { - $options['headers'][] = 'User-Agent: Symfony HttpClient/Amp'; + $options['headers'][] = 'User-Agent: Symfony HttpClient (Amp)'; } if (0 < $options['max_duration']) { @@ -153,7 +153,7 @@ public function stream(ResponseInterface|iterable $responses, float $timeout = n return new ResponseStream(AmpResponse::stream($responses, $timeout)); } - public function reset() + public function reset(): void { $this->multi->dnsCache = []; diff --git a/src/Symfony/Component/HttpClient/CHANGELOG.md b/src/Symfony/Component/HttpClient/CHANGELOG.md index d6d50d2d5f9d7..7cd69191c5411 100644 --- a/src/Symfony/Component/HttpClient/CHANGELOG.md +++ b/src/Symfony/Component/HttpClient/CHANGELOG.md @@ -1,6 +1,15 @@ CHANGELOG ========= +6.3 +--- + + * Add `UriTemplateHttpClient` to use URI templates as specified in the RFC 6570 + * Ad 10000 d `ServerSentEvent::getArrayData()` to get the Server-Sent Event's data decoded as an array when it's a JSON payload + * Allow array of urls as `base_uri` option value in `RetryableHttpClient` to retry on a new url each time + * Add `JsonMockResponse`, a `MockResponse` shortcut that automatically encodes the passed body to JSON and sets the content type to `application/json` by default + * Support file uploads by nesting resource streams in option "body" + 6.2 --- diff --git a/src/Symfony/Component/HttpClient/CachingHttpClient.php b/src/Symfony/Component/HttpClient/CachingHttpClient.php index 5e3fbf507501c..0b6e49580615e 100644 --- a/src/Symfony/Component/HttpClient/CachingHttpClient.php +++ b/src/Symfony/Component/HttpClient/CachingHttpClient.php @@ -52,6 +52,7 @@ public function __construct(HttpClientInterface $client, StoreInterface $store, unset($defaultOptions['debug']); unset($defaultOptions['default_ttl']); unset($defaultOptions['private_headers']); + unset($defaultOptions['skip_response_headers']); unset($defaultOptions['allow_reload']); unset($defaultOptions['allow_revalidate']); unset($defaultOptions['stale_while_revalidate']); @@ -135,6 +136,9 @@ public function stream(ResponseInterface|iterable $responses, float $timeout = n })()); } + /** + * @return void + */ public function reset() { if ($this->client instanceof ResetInterface) { diff --git a/src/Symfony/Component/HttpClient/Chunk/ServerSentEvent.php b/src/Symfony/Component/HttpClient/Chunk/ServerSentEvent.php index 296918e6aff73..7231506184451 100644 --- a/src/Symfony/Component/HttpClient/Chunk/ServerSentEvent.php +++ b/src/Symfony/Component/HttpClient/Chunk/ServerSentEvent.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpClient\Chunk; +use Symfony\Component\HttpClient\Exception\JsonException; use Symfony\Contracts\HttpClient\ChunkInterface; /** @@ -23,6 +24,7 @@ final class ServerSentEvent extends DataChunk implements ChunkInterface private string $id = ''; private string $type = 'message'; private float $retry = 0; + private ?array $jsonData = null; public function __construct(string $content) { @@ -76,4 +78,30 @@ public function getRetry(): float { return $this->retry; } + + /** + * Gets the SSE data decoded as an array when it's a JSON payload. + */ + public function getArrayData(): array + { + if (null !== $this->jsonData) { + return $this->jsonData; + } + + if ('' === $this->data) { + throw new JsonException(sprintf('Server-Sent Event%s data is empty.', '' !== $this->id ? sprintf(' "%s"', $this->id) : '')); + } + + try { + $jsonData = json_decode($this->data, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new JsonException(sprintf('Decoding Server-Sent Event%s failed: ', '' !== $this->id ? sprintf(' "%s"', $this->id) : '').$e->getMessage(), $e->getCode()); + } + + if (!\is_array($jsonData)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned in Server-Sent Event%s.', get_debug_type($jsonData), '' !== $this->id ? sprintf(' "%s"', $this->id) : '')); + } + + return $this->jsonData = $jsonData; + } } diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 9e87e15d845be..5c19ed0bf1e69 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -93,7 +93,7 @@ public function request(string $method, string $url, array $options = []): Respo $url = implode('', $url); if (!isset($options['normalized_headers']['user-agent'])) { - $options['headers'][] = 'User-Agent: Symfony HttpClient/Curl'; + $options['headers'][] = 'User-Agent: Symfony HttpClient (Curl)'; } $curlopts = [ @@ -221,9 +221,10 @@ public function request(string $method, string $url, array $options = []): Respo if (\is_resource($body)) { $curlopts[\CURLOPT_INFILE] = $body; } else { - $eof = false; - $buffer = ''; - $curlopts[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body, &$buffer, &$eof) { + $curlopts[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) { + static $eof = false; + static $buffer = ''; + return self::readRequestBody($length, $body, $buffer, $eof); }; } @@ -321,7 +322,7 @@ public function stream(ResponseInterface|iterable $responses, float $timeout = n return new ResponseStream(CurlResponse::stream($responses, $timeout)); } - public function reset() + public function reset(): void { $this->multi->reset(); } @@ -386,14 +387,10 @@ private static function createRedirectResolver(array $options, string $host, int if (0 < $options['max_redirects']) { $redirectHeaders['host'] = $host; $redirectHeaders['port'] = $port; - $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { - return 0 !== stripos($h, 'Host:'); - }); + $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static fn ($h) => 0 !== stripos($h, 'Host:')); if (isset($options['normalized_headers']['authorization'][0]) || isset($options['normalized_headers']['cookie'][0])) { - $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { - return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:'); - }); + $redirectHeaders['no_auth'] = array_filter($options['headers'], static fn ($h) => 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:')); } } @@ -405,9 +402,7 @@ private static function createRedirectResolver(array $options, string $host, int } if ($noContent && $redirectHeaders) { - $filterContentHeaders = static function ($h) { - return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); - }; + $filterContentHeaders = static fn ($h) => 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); } @@ -431,9 +426,7 @@ private static function createRedirectResolver(array $options, string $host, int private function findConstantName(int $opt): ?string { - $constants = array_filter(get_defined_constants(), static function ($v, $k) use ($opt) { - return $v === $opt && 'C' === $k[0] && (str_starts_with($k, 'CURLOPT_') || str_starts_with($k, 'CURLINFO_')); - }, \ARRAY_FILTER_USE_BOTH); + $constants = array_filter(get_defined_constants(), static fn ($v, $k) => $v === $opt && 'C' === $k[0] && (str_starts_with($k, 'CURLOPT_') || str_starts_with($k, 'CURLINFO_')), \ARRAY_FILTER_USE_BOTH); return key($constants); } diff --git a/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php b/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php index 5242517293330..68101fc2e9174 100644 --- a/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php +++ b/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php @@ -31,17 +31,17 @@ final class HttpClientDataCollector extends DataCollector implements LateDataCol */ private array $clients = []; - public function registerClient(string $name, TraceableHttpClient $client) + public function registerClient(string $name, TraceableHttpClient $client): void { $this->clients[$name] = $client; } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { $this->lateCollect(); } - public function lateCollect() + public function lateCollect(): void { $this->data['request_count'] = $this->data['request_count'] ?? 0; $this->data['error_count'] = $this->data['error_count'] ?? 0; @@ -86,7 +86,7 @@ public function getName(): string return 'http_client'; } - public function reset() + public function reset(): void { $this->data = [ 'clients' => [], diff --git a/src/Symfony/Component/HttpClient/DecoratorTrait.php b/src/Symfony/Component/HttpClient/DecoratorTrait.php index f38664b4353a7..472437e465b13 100644 --- a/src/Symfony/Component/HttpClient/DecoratorTrait.php +++ b/src/Symfony/Component/HttpClient/DecoratorTrait.php @@ -48,6 +48,9 @@ public function withOptions(array $options): static return $clone; } + /** + * @return void + */ public function reset() { if ($this->client instanceof ResetInterface) { diff --git a/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php b/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php index c42d873fc4f5c..214a655bc6992 100644 --- a/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php +++ b/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php @@ -19,7 +19,7 @@ final class HttpClientPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('data_collector.http_client')) { return; diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index b081eaff24fb5..c767ca81aac01 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -13,6 +13,9 @@ use Symfony\Component\HttpClient\Exception\InvalidArgumentException; use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Response\StreamableInterface; +use Symfony\Component\HttpClient\Response\StreamWrapper; +use Symfony\Component\Mime\MimeTypes; /** * Provides the common logic from writing HttpClientInterface implementations. @@ -94,11 +97,7 @@ private static function prepareRequest(?string $method, ?string $url, array $opt } if (isset($options['body'])) { - if (\is_array($options['body']) && (!isset($options['normalized_headers']['content-type'][0]) || !str_contains($options['normalized_headers']['content-type'][0], 'application/x-www-form-urlencoded'))) { - $options['normalized_headers']['content-type'] = ['Content-Type: application/x-www-form-urlencoded']; - } - - $options['body'] = self::normalizeBody($options['body']); + $options['body'] = self::normalizeBody($options['body'], $options['normalized_headers']); if (\is_string($options['body']) && (string) \strlen($options['body']) !== substr($h = $options['normalized_headers']['content-length'][0] ?? '', 16) @@ -245,6 +244,10 @@ private static function mergeDefaultOptions(array $options, array $defaultOption throw new InvalidArgumentException(sprintf('Option "auth_ntlm" is not supported by "%s", '.$msg, __CLASS__, CurlHttpClient::class)); } + if ('vars' === $name) { + throw new InvalidArgumentException(sprintf('Option "vars" is not supported by "%s", try using "%s" instead.', __CLASS__, UriTemplateHttpClient::class)); + } + $alternatives = []; foreach ($defaultOptions as $k => $v) { @@ -309,40 +312,146 @@ private static function normalizeHeaders(array $headers): array * * @throws InvalidArgumentException When an invalid body is passed */ - private static function normalizeBody($body) + private static function normalizeBody($body, array &$normalizedHeaders = []) { if (\is_array($body)) { - array_walk_recursive($body, $caster = static function (&$v) use (&$caster) { - if (\is_object($v)) { + static $cookie; + + $streams = []; + array_walk_recursive($body, $caster = static function (&$v) use (&$caster, &$streams, &$cookie) { + if (\is_resource($v) || $v instanceof StreamableInterface) { + $cookie = hash('xxh128', $cookie ??= random_bytes(8), true); + $k = substr(strtr(base64_encode($cookie), '+/', '-_'), 0, -2); + $streams[$k] = $v instanceof StreamableInterface ? $v->toStream(false) : $v; + $v = $k; + } elseif (\is_object($v)) { if ($vars = get_object_vars($v)) { array_walk_recursive($vars, $caster); $v = $vars; - } elseif (method_exists($v, '__toString')) { + } elseif ($v instanceof \Stringable) { $v = (string) $v; } } }); - return http_build_query($body, '', '&'); + $body = http_build_query($body, '', '&'); + + if ('' === $body || !$streams && !str_contains($normalizedHeaders['content-type'][0] ?? '', 'multipart/form-data')) { + if (!str_contains($normalizedHeaders['content-type'][0] ?? '', 'application/x-www-form-urlencoded')) { + $normalizedHeaders['content-type'] = ['Content-Type: application/x-www-form-urlencoded']; + } + + return $body; + } + + if (preg_match('{multipart/form-data; boundary=(?|"([^"\r\n]++)"|([-!#$%&\'*+.^_`|~_A-Za-z0-9]++))}', $normalizedHeaders['content-type'][0] ?? '', $boundary)) { + $boundary = $boundary[1]; + } else { + $boundary = substr(strtr(base64_encode($cookie ??= random_bytes(8)), '+/', '-_'), 0, -2); + $normalizedHeaders['content-type'] = ['Content-Type: multipart/form-data; boundary='.$boundary]; + } + + $body = explode('&', $body); + $contentLength = 0; + + foreach ($body as $i => $part) { + [$k, $v] = explode('=', $part, 2); + $part = ($i ? "\r\n" : '')."--{$boundary}\r\n"; + $k = str_replace(['"', "\r", "\n"], ['%22', '%0D', '%0A'], urldecode($k)); // see WHATWG HTML living standard + + if (!isset($streams[$v])) { + $part .= "Content-Disposition: form-data; name=\"{$k}\"\r\n\r\n".urldecode($v); + $contentLength += 0 <= $contentLength ? \strlen($part) : 0; + $body[$i] = [$k, $part, null]; + continue; + } + $v = $streams[$v]; + + if (!\is_array($m = @stream_get_meta_data($v))) { + throw new TransportException(sprintf('Invalid "%s" resource found in body part "%s".', get_resource_type($v), $k)); + } + if (feof($v)) { + throw new TransportException(sprintf('Uploaded stream ended for body part "%s".', $k)); + } + + $m += stream_context_get_options($v)['http'] ?? []; + $filename = basename($m['filename'] ?? $m['uri'] ?? 'unknown'); + $filename = str_replace(['"', "\r", "\n"], ['%22', '%0D', '%0A'], $filename); + $contentType = $m['content_type'] ?? null; + + if (($headers = $m['wrapper_data'] ?? []) instanceof StreamWrapper) { + $hasContentLength = false; + $headers = $headers->getResponse()->getInfo('response_headers'); + } elseif ($hasContentLength = 0 < $h = fstat($v)['size'] ?? 0) { + $contentLength += 0 <= $contentLength ? $h : 0; + } + + foreach (\is_array($headers) ? $headers : [] as $h) { + if (\is_string($h) && 0 === stripos($h, 'Content-Type: ')) { + $contentType ??= substr($h, 14); + } elseif (!$hasContentLength && \is_string($h) && 0 === stripos($h, 'Content-Length: ')) { + $hasContentLength = true; + $contentLength += 0 <= $contentLength ? substr($h, 16) : 0; + } elseif (\is_string($h) && 0 === stripos($h, 'Content-Encoding: ')) { + $contentLength = -1; + } + } + + if (!$hasContentLength) { + $contentLength = -1; + } + if (null === $contentType && 'plainfile' === ($m['wrapper_type'] ?? null) && isset($m['uri'])) { + $mimeTypes = class_exists(MimeTypes::class) ? MimeTypes::getDefault() : false; + $contentType = $mimeTypes ? $mimeTypes->guessMimeType($m['uri']) : null; + } + $contentType ??= 'application/octet-stream'; + + $part .= "Content-Disposition: form-data; name=\"{$k}\"; filename=\"{$filename}\"\r\n"; + $part .= "Content-Type: {$contentType}\r\n\r\n"; + + $contentLength += 0 <= $contentLength ? \strlen($part) : 0; + $body[$i] = [$k, $part, $v]; + } + + $body[++$i] = ['', "\r\n--{$boundary}--\r\n", null]; + + if (0 < $contentLength) { + $normalizedHeaders['content-length'] = ['Content-Length: '.($contentLength += \strlen($body[$i][1]))]; + } + + $body = static function ($size) use ($body) { + foreach ($body as $i => [$k, $part, $h]) { + unset($body[$i]); + + yield $part; + + while (null !== $h && !feof($h)) { + if (false === $part = fread($h, $size)) { + throw new TransportException(sprintf('Error while reading uploaded stream for body part "%s".', $k)); + } + + yield $part; + } + } + $h = null; + }; } if (\is_string($body)) { return $body; } - $generatorToCallable = static function (\Generator $body): \Closure { - return static function () use ($body) { - while ($body->valid()) { - $chunk = $body->current(); - $body->next(); + $generatorToCallable = static fn (\Generator $body): \Closure => static function () use ($body) { + while ($body->valid()) { + $chunk = $body->current(); + $body->next(); - if ('' !== $chunk) { - return $chunk; - } + if ('' !== $chunk) { + return $chunk; } + } - return ''; - }; + return ''; }; if ($body instanceof \Generator) { @@ -536,11 +645,11 @@ private static function parseUrl(string $url, array $query = [], array $allowedS if (str_contains($parts[$part], '%')) { // https://tools.ietf.org/html/rfc3986#section-2.3 - $parts[$part] = preg_replace_callback('/%(?:2[DE]|3[0-9]|[46][1-9A-F]|5F|[57][0-9A]|7E)++/i', function ($m) { return rawurldecode($m[0]); }, $parts[$part]); + $parts[$part] = preg_replace_callback('/%(?:2[DE]|3[0-9]|[46][1-9A-F]|5F|[57][0-9A]|7E)++/i', fn ($m) => rawurldecode($m[0]), $parts[$part]); } // https://tools.ietf.org/html/rfc3986#section-3.3 - $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()[\]*+,;=:@\\\\^`{|}%]++#", function ($m) { return rawurlencode($m[0]); }, $parts[$part]); + $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()[\]*+,;=:@\\\\^`{|}%]++#", fn ($m) => rawurlencode($m[0]), $parts[$part]); } return [ @@ -556,6 +665,8 @@ private static function parseUrl(string $url, array $query = [], array $allowedS * Removes dot-segments from a path. * * @see https://tools.ietf.org/html/rfc3986#section-5.2.4 + * + * @return string */ private static function removeDotSegments(string $path) { diff --git a/src/Symfony/Component/HttpClient/HttpOptions.php b/src/Symfony/Component/HttpClient/HttpOptions.php index a07fac7eda833..57590d3c131fc 100644 --- a/src/Symfony/Component/HttpClient/HttpOptions.php +++ b/src/Symfony/Component/HttpClient/HttpOptions.php @@ -135,6 +135,16 @@ public function setBaseUri(string $uri): static return $this; } + /** + * @return $this + */ + public function setVars(array $vars): static + { + $this->options['vars'] = $vars; + + return $this; + } + /** * @return $this */ diff --git a/src/Symfony/Component/HttpClient/HttplugClient.php b/src/Symfony/Component/HttpClient/HttplugClient.php index 4d539219174be..6cb815bd2d196 100644 --- a/src/Symfony/Component/HttpClient/HttplugClient.php +++ b/src/Symfony/Component/HttpClient/HttplugClient.php @@ -26,6 +26,7 @@ use Nyholm\Psr7\Factory\Psr17Factory; use Nyholm\Psr7\Request; use Nyholm\Psr7\Uri; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseFactoryInterface; @@ -53,6 +54,10 @@ throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-factory" package is not installed. Try running "composer require nyholm/psr7".'); } +if (!interface_exists(ClientInterface::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".'); +} + /** * An adapter to turn a Symfony HttpClientInterface into an Httplug client. * @@ -61,7 +66,7 @@ * * @author Nicolas Grekas */ -final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, RequestFactory, StreamFactory, UriFactory, ResetInterface +final class HttplugClient implements ClientInterface, HttplugInterface, HttpAsyncClient, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, RequestFactory, StreamFactory, UriFactory, ResetInterface { private HttpClientInterface $client; private ResponseFactoryInterface $responseFactory; @@ -266,7 +271,7 @@ public function __destruct() $this->wait(); } - public function reset() + public function reset(): void { if ($this->client instanceof ResetInterface) { $this->client->reset(); diff --git a/src/Symfony/Component/HttpClient/Internal/AmpClientState.php b/src/Symfony/Component/HttpClient/Internal/AmpClientState.php index 18a1722c38115..a83176629b9d3 100644 --- a/src/Symfony/Component/HttpClient/Internal/AmpClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/AmpClientState.php @@ -94,9 +94,7 @@ public function request(array $options, Request $request, CancellationToken $can } $request->addEventListener(new AmpListener($info, $options['peer_fingerprint']['pin-sha256'] ?? [], $onProgress, $handle)); - $request->setPushHandler(function ($request, $response) use ($options): Promise { - return $this->handlePush($request, $response, $options); - }); + $request->setPushHandler(fn ($request, $response): Promise => $this->handlePush($request, $response, $options)); ($request->hasHeader('content-length') ? new Success((int) $request->getHeader('content-length')) : $request->getBody()->getBodyLength()) ->onResolve(static function ($e, $bodySize) use (&$info) { @@ -130,7 +128,7 @@ private function getClient(array $options): array 'proxy' => $options['proxy'], ]; - $key = md5(serialize($options)); + $key = hash('xxh128', serialize($options)); if (isset($this->clients[$key])) { return $this->clients[$key]; diff --git a/src/Symfony/Component/HttpClient/Internal/Canary.php b/src/Symfony/Component/HttpClient/Internal/Canary.php index 968764019a57d..b4438d94d0e37 100644 --- a/src/Symfony/Component/HttpClient/Internal/Canary.php +++ b/src/Symfony/Component/HttpClient/Internal/Canary.php @@ -25,7 +25,7 @@ public function __construct(\Closure $canceller) $this->canceller = $canceller; } - public function cancel() + public function cancel(): void { if (isset($this->canceller)) { $canceller = $this->canceller; diff --git a/src/Symfony/Component/HttpClient/Internal/CurlClientState.php b/src/Symfony/Component/HttpClient/Internal/CurlClientState.php index 8e2a42ed008ba..bcf1f92ab4840 100644 --- a/src/Symfony/Component/HttpClient/Internal/CurlClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/CurlClientState.php @@ -75,12 +75,10 @@ public function __construct(int $maxHostConnections, int $maxPendingPushes) $multi->handlesActivity = &$this->handlesActivity; $multi->openHandles = &$this->openHandles; - curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes) { - return $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes); - }); + curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, static fn ($parent, $pushed, array $requestHeaders) => $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes)); } - public function reset() + public function reset(): void { foreach ($this->pushedResponses as $url => $response) { $this->logger?->debug(sprintf('Unused pushed response: "%s"', $url)); diff --git a/src/Symfony/Component/HttpClient/Internal/NativeClientState.php b/src/Symfony/Component/HttpClient/Internal/NativeClientState.php index b44191e93c7f3..75f23a9a7e8ca 100644 --- a/src/Symfony/Component/HttpClient/Internal/NativeClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/NativeClientState.php @@ -34,7 +34,7 @@ public function __construct() $this->id = random_int(\PHP_INT_MIN, \PHP_INT_MAX); } - public function reset() + public function reset(): void { $this->responseCount = 0; $this->dnsCache = []; diff --git a/src/Symfony/Component/HttpClient/MockHttpClient.php b/src/Symfony/Component/HttpClient/MockHttpClient.php index 7906b9a96c418..5d8a2dccffe5f 100644 --- a/src/Symfony/Component/HttpClient/MockHttpClient.php +++ b/src/Symfony/Component/HttpClient/MockHttpClient.php @@ -69,7 +69,7 @@ public function request(string $method, string $url, array $options = []): Respo } elseif (\is_callable($this->responseFactory)) { $response = ($this->responseFactory)($method, $url, $options); } elseif (!$this->responseFactory->valid()) { - throw new TransportException('The response factory iterator passed to MockHttpClient is empty.'); + throw new TransportException($this->requestsCount ? 'No more response left in the response factory iterator passed to MockHttpClient: the number of requests exceeds the number of responses.' : 'The response factory iterator passed to MockHttpClient is empty.'); } else { $responseFactory = $this->responseFactory->current(); $response = \is_callable($responseFactory) ? $responseFactory($method, $url, $options) : $responseFactory; @@ -106,6 +106,9 @@ public function withOptions(array $options): static return $clone; } + /** + * @return void + */ public function reset() { $this->requestsCount = 0; diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index 734effbb40354..e4a2b0c28c67e 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -132,10 +132,8 @@ public function request(string $method, string $url, array $options = []): Respo ]; if ($onProgress = $options['on_progress']) { - // Memoize the last progress to ease calling the callback periodically when no network transfer happens - $lastProgress = [0, 0]; $maxDuration = 0 < $options['max_duration'] ? $options['max_duration'] : \INF; - $onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info, $maxDuration) { + $onProgress = static function (...$progress) use ($onProgress, &$info, $maxDuration) { if ($info['total_time'] >= $maxDuration) { throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url']))); } @@ -144,6 +142,9 @@ public function request(string $method, string $url, array $options = []): Respo $progressInfo['url'] = implode('', $info['url']); unset($progressInfo['size_body']); + // Memoize the last progress to ease calling the callback periodically when no network transfer happens + static $lastProgress = [0, 0]; + if ($progress && -1 === $progress[0]) { // Response completed $lastProgress[0] = max($lastProgress); @@ -190,7 +191,7 @@ public function request(string $method, string $url, array $options = []): Respo $this->logger?->info(sprintf('Request: "%s %s"', $method, implode('', $url))); if (!isset($options['normalized_headers']['user-agent'])) { - $options['headers'][] = 'User-Agent: Symfony HttpClient/Native'; + $options['headers'][] = 'User-Agent: Symfony HttpClient (Native)'; } if (0 < $options['max_duration']) { @@ -223,7 +224,7 @@ public function request(string $method, string $url, array $options = []): Respo 'allow_self_signed' => (bool) $options['peer_fingerprint'], 'SNI_enabled' => true, 'disable_compression' => true, - ], static function ($v) { return null !== $v; }), + ], static fn ($v) => null !== $v), 'socket' => [ 'bindto' => $options['bindto'], 'tcp_nodelay' => true, @@ -261,7 +262,7 @@ public function stream(ResponseInterface|iterable $responses, float $timeout = n return new ResponseStream(NativeResponse::stream($responses, $timeout)); } - public function reset() + public function reset(): void { $this->multi->reset(); } @@ -342,14 +343,10 @@ private static function createRedirectResolver(array $options, string $host, str $redirectHeaders = []; if (0 < $maxRedirects = $options['max_redirects']) { $redirectHeaders = ['host' => $host, 'port' => $port]; - $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { - return 0 !== stripos($h, 'Host:'); - }); + $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static fn ($h) => 0 !== stripos($h, 'Host:')); if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) { - $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) { - return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:'); - }); + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static fn ($h) => 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:')); } } @@ -386,9 +383,7 @@ private static function createRedirectResolver(array $options, string $host, str if ('POST' === $options['method'] || 303 === $info['http_code']) { $info['http_method'] = $options['method'] = 'HEAD' === $options['method'] ? 'HEAD' : 'GET'; $options['content'] = ''; - $filterContentHeaders = static function ($h) { - return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); - }; + $filterContentHeaders = static fn ($h) => 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); $options['header'] = array_filter($options['header'], $filterContentHeaders); $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); diff --git a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php index 89885abe1f4f7..70c172f68678e 100644 --- a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php +++ b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php @@ -30,21 +30,6 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa { use HttpClientTrait; - private const PRIVATE_SUBNETS = [ - '127.0.0.0/8', - '10.0.0.0/8', - '192.168.0.0/16', - '172.16.0.0/12', - '169.254.0.0/16', - '0.0.0.0/8', - '240.0.0.0/4', - '::1/128', - 'fc00::/7', - 'fe80::/10', - '::ffff:0:0/96', - '::/128', - ]; - private HttpClientInterface $client; private string|array|null $subnets; @@ -70,11 +55,11 @@ public function request(string $method, string $url, array $options = []): Respo } $subnets = $this->subnets; - $lastPrimaryIp = ''; - $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets, &$lastPrimaryIp): void { + $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets): void { + static $lastPrimaryIp = ''; if ($info['primary_ip'] !== $lastPrimaryIp) { - if ($info['primary_ip'] && IpUtils::checkIp($info['primary_ip'], $subnets ?? self::PRIVATE_SUBNETS)) { + if ($info['primary_ip'] && IpUtils::checkIp($info['primary_ip'], $subnets ?? IpUtils::PRIVATE_SUBNETS)) { throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $info['primary_ip'], $info['url'])); } @@ -107,7 +92,7 @@ public function withOptions(array $options): static return $clone; } - public function reset() + public function reset(): void { if ($this->client instanceof ResetInterface) { $this->client->reset(); diff --git a/src/Symfony/Component/HttpClient/Psr18Client.php b/src/Symfony/Component/HttpClient/Psr18Client.php index 699acf4498a1f..0be916acb8d9f 100644 --- a/src/Symfony/Component/HttpClient/Psr18Client.php +++ b/src/Symfony/Component/HttpClient/Psr18Client.php @@ -191,7 +191,7 @@ public function createUri(string $uri = ''): UriInterface throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); } - public function reset() + public function reset(): void { if ($this->client instanceof ResetInterface) { $this->client->reset(); diff --git a/src/Symfony/Component/HttpClient/Response/AmpResponse.php b/src/Symfony/Component/HttpClient/Response/AmpResponse.php index d46e4036d801c..e263b7782627e 100644 --- a/src/Symfony/Component/HttpClient/Response/AmpResponse.php +++ b/src/Symfony/Component/HttpClient/Response/AmpResponse.php @@ -67,9 +67,7 @@ public function __construct(AmpClientState $multi, Request $request, array $opti $request->setHeader('Accept-Encoding', 'gzip'); } - $this->initializer = static function (self $response) { - return null !== $response->options; - }; + $this->initializer = static fn (self $response) => null !== $response->options; $info = &$this->info; $headers = &$this->headers; @@ -103,7 +101,7 @@ public function __construct(AmpClientState $multi, Request $request, array $opti $throttleWatcher = null; $this->id = $id = self::$nextId++; - Loop::defer(static function () use ($request, $multi, &$id, &$info, &$headers, $canceller, &$options, $onProgress, &$handle, $logger, &$pause) { + Loop::defer(static function () use ($request, $multi, $id, &$info, &$headers, $canceller, &$options, $onProgress, &$handle, $logger, &$pause) { return new Coroutine(self::generateResponse($request, $multi, $id, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause)); }); diff --git a/src/Symfony/Component/HttpClient/Response/AsyncResponse.php b/src/Symfony/Component/HttpClient/Response/AsyncResponse.php index 8236623b2793f..66152e3d90c7e 100644 --- a/src/Symfony/Component/HttpClient/Response/AsyncResponse.php +++ b/src/Symfony/Component/HttpClient/Response/AsyncResponse.php @@ -123,6 +123,9 @@ public function getInfo(string $type = null): mixed return $this->info + $this->response->getInfo(); } + /** + * @return resource + */ public function toStream(bool $throw = true) { if ($throw) { diff --git a/src/Symfony/Component/HttpClient/Response/CommonResponseTrait.php b/src/Symfony/Component/HttpClient/Response/CommonResponseTrait.php index 06f6b7d88a33c..08a833dfecf9b 100644 --- a/src/Symfony/Component/HttpClient/Response/CommonResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/CommonResponseTrait.php @@ -100,6 +100,9 @@ public function toArray(bool $throw = true): array return $content; } + /** + * @return resource + */ public function toStream(bool $throw = true) { if ($throw) { @@ -153,7 +156,7 @@ private static function initialize(self $response): void $response->initializer = null; } - private function checkStatusCode() + private function checkStatusCode(): void { $code = $this->getInfo('http_code'); diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php index bfea82b6ffa5c..546aa0d78bb85 100644 --- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php +++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php @@ -167,7 +167,7 @@ public function __construct(CurlClientState $multi, \CurlHandle|string $ch, arra }); $this->initializer = static function (self $response) { - $waitFor = curl_getinfo($ch = $response->handle, \CURLINFO_PRIVATE); + $waitFor = curl_getinfo($response->handle, \CURLINFO_PRIVATE); return 'H' === $waitFor[0]; }; @@ -266,7 +266,7 @@ private static function schedule(self $response, array &$runningResponses): void $runningResponses[$i] = [$response->multi, [$response->id => $response]]; } - if ('_0' === curl_getinfo($ch = $response->handle, \CURLINFO_PRIVATE)) { + if ('_0' === curl_getinfo($response->handle, \CURLINFO_PRIVATE)) { // Response already completed $response->multi->handlesActivity[$response->id][] = null; $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; diff --git a/src/Symfony/Component/HttpClient/Response/HttplugPromise.php b/src/Symfony/Component/HttpClient/Response/HttplugPromise.php index deaea720da735..e9dc24041e5fa 100644 --- a/src/Symfony/Component/HttpClient/Response/HttplugPromise.php +++ b/src/Symfony/Component/HttpClient/Response/HttplugPromise.php @@ -68,8 +68,6 @@ private function wrapThenCallback(?callable $callback): ?callable return null; } - return static function ($value) use ($callback) { - return Create::promiseFor($callback($value)); - }; + return static fn ($value) => Create::promiseFor($callback($value)); } } diff --git a/src/Symfony/Component/HttpClient/Response/JsonMockResponse.php b/src/Symfony/Component/HttpClient/Response/JsonMockResponse.php new file mode 100644 index 0000000000000..d09f66f9dd968 --- /dev/null +++ b/src/Symfony/Component/HttpClient/Response/JsonMockResponse.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; + +class JsonMockResponse extends MockResponse +{ + public function __construct(mixed $body = [], array $info = []) + { + try { + $json = json_encode($body, \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new InvalidArgumentException('JSON encoding failed: '.$e->getMessage(), $e->getCode(), $e); + } + + $info['response_headers']['content-type'] ??= 'application/json'; + + parent::__construct($json, $info); + } +} diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php index 24089013f46f7..4c21eba91e6b0 100644 --- a/src/Symfony/Component/HttpClient/Response/MockResponse.php +++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php @@ -26,9 +26,7 @@ class MockResponse implements ResponseInterface, StreamableInterface { use CommonResponseTrait; - use TransportResponseTrait { - doDestruct as public __destruct; - } + use TransportResponseTrait; private string|iterable $body; private array $requestOptions = []; @@ -110,6 +108,11 @@ public function cancel(): void $onProgress($this->offset, $dlSize, $this->info); } + public function __destruct() + { + $this->doDestruct(); + } + protected function close(): void { $this->inflate = null; @@ -125,9 +128,7 @@ public static function fromRequest(string $method, string $url, array $options, $response->requestOptions = $options; $response->id = ++self::$idSequence; $response->shouldBuffer = $options['buffer'] ?? true; - $response->initializer = static function (self $response) { - return \is_array($response->body[0] ?? null); - }; + $response->initializer = static fn (self $response) => \is_array($response->body[0] ?? null); $response->info['redirect_count'] = 0; $response->info['redirect_url'] = null; @@ -190,11 +191,6 @@ protected static function perform(ClientState $multi, array &$responses): void $chunk[1]->getHeaders(false); self::readResponse($response, $chunk[0], $chunk[1], $offset); $multi->handlesActivity[$id][] = new FirstChunk(); - $buffer = $response->requestOptions['buffer'] ?? null; - - if ($buffer instanceof \Closure && $response->content = $buffer($response->headers) ?: null) { - $response->content = \is_resource($response->content) ? $response->content : fopen('php://temp', 'w+'); - } } catch (\Throwable $e) { $multi->handlesActivity[$id][] = null; $multi->handlesActivity[$id][] = $e; diff --git a/src/Symfony/Component/HttpClient/Response/NativeResponse.php b/src/Symfony/Component/HttpClient/Response/NativeResponse.php index a6b9dd989bebb..3d2b26dae112c 100644 --- a/src/Symfony/Component/HttpClient/Response/NativeResponse.php +++ b/src/Symfony/Component/HttpClient/Response/NativeResponse.php @@ -71,9 +71,7 @@ public function __construct(NativeClientState $multi, $context, string $url, arr $info['max_duration'] = $options['max_duration']; ++$multi->responseCount; - $this->initializer = static function (self $response) { - return null === $response->remaining; - }; + $this->initializer = static fn (self $response) => null === $response->remaining; $pauseExpiry = &$this->pauseExpiry; $info['pause_handler'] = static function (float $duration) use (&$pauseExpiry) { diff --git a/src/Symfony/Component/HttpClient/Response/StreamWrapper.php b/src/Symfony/Component/HttpClient/Response/StreamWrapper.php index f891313dd3c88..b5554a8ad2ced 100644 --- a/src/Symfony/Component/HttpClient/Response/StreamWrapper.php +++ b/src/Symfony/Component/HttpClient/Response/StreamWrapper.php @@ -32,7 +32,7 @@ class StreamWrapper /** @var resource|string|null */ private $content; - /** @var resource|null */ + /** @var resource|callable|null */ private $handle; private bool $blocking = true; @@ -266,6 +266,9 @@ public function stream_seek(int $offset, int $whence = \SEEK_SET): bool return false; } + /** + * @return resource|false + */ public function stream_cast(int $castAs) { if (\STREAM_CAST_FOR_SELECT === $castAs) { diff --git a/src/Symfony/Component/HttpClient/RetryableHttpClient.php b/src/Symfony/Component/HttpClient/RetryableHttpClient.php index d0c13165be59e..1a6ec7d35e63f 100644 --- a/src/Symfony/Component/HttpClient/RetryableHttpClient.php +++ b/src/Symfony/Component/HttpClient/RetryableHttpClient.php @@ -35,6 +35,7 @@ class RetryableHttpClient implements HttpClientInterface, ResetInterface private RetryStrategyInterface $strategy; private int $maxRetries; private LoggerInterface $logger; + private array $baseUris = []; /** * @param int $maxRetries The maximum number of times to retry @@ -47,17 +48,38 @@ public function __construct(HttpClientInterface $client, RetryStrategyInterface $this->logger = $logger ?? new NullLogger(); } + public function withOptions(array $options): static + { + if (\array_key_exists('base_uri', $options)) { + if (\is_array($options['base_uri'])) { + $this->baseUris = $options['base_uri']; + unset($options['base_uri']); + } else { + $this->baseUris = []; + } + } + + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } + public function request(string $method, string $url, array $options = []): ResponseInterface { + $baseUris = \array_key_exists('base_uri', $options) ? $options['base_uri'] : $this->baseUris; + $baseUris = \is_array($baseUris) ? $baseUris : []; + $options = self::shiftBaseUri($options, $baseUris); + if ($this->maxRetries <= 0) { return new AsyncResponse($this->client, $method, $url, $options); } - $retryCount = 0; - $content = ''; - $firstChunk = null; + return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, &$baseUris) { + static $retryCount = 0; + static $content = ''; + static $firstChunk; - return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, &$retryCount, &$content, &$firstChunk) { $exception = null; try { if ($context->getInfo('canceled') || $chunk->isTimeout() || null !== $chunk->getInformationalStatus()) { @@ -73,7 +95,7 @@ public function request(string $method, string $url, array $options = []): Respo if ('' !== $context->getInfo('primary_ip')) { $shouldRetry = $this->strategy->shouldRetry($context, null, $exception); if (null === $shouldRetry) { - throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with an exception.', \get_class($this->strategy))); + throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with an exception.', $this->strategy::class)); } if (false === $shouldRetry) { @@ -104,7 +126,7 @@ public function request(string $method, string $url, array $options = []): Respo } if (null === $shouldRetry = $this->strategy->shouldRetry($context, $content, null)) { - throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with a body.', \get_class($this->strategy))); + throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with a body.', $this->strategy::class)); } if (false === $shouldRetry) { @@ -127,7 +149,7 @@ public function request(string $method, string $url, array $options = []): Respo ]); $context->setInfo('retry_count', $retryCount); - $context->replaceRequest($method, $url, $options); + $context->replaceRequest($method, $url, self::shiftBaseUri($options, $baseUris)); $context->pause($delay / 1000); if ($retryCount >= $this->maxRetries) { @@ -168,4 +190,14 @@ private function passthru(AsyncContext $context, ?ChunkInterface $firstChunk, st yield $lastChunk; } + + private static function shiftBaseUri(array $options, array &$baseUris): array + { + if ($baseUris) { + $baseUri = 1 < \count($baseUris) ? array_shift($baseUris) : current($baseUris); + $options['base_uri'] = \is_array($baseUri) ? $baseUri[array_rand($baseUri)] : $baseUri; + } + + return $options; + } } diff --git a/src/Symfony/Component/HttpClient/ScopingHttpClient.php b/src/Symfony/Component/HttpClient/ScopingHttpClient.php index da8ef4c98ecec..a87171d2cad68 100644 --- a/src/Symfony/Component/HttpClient/ScopingHttpClient.php +++ b/src/Symfony/Component/HttpClient/ScopingHttpClient.php @@ -93,6 +93,9 @@ public function stream(ResponseInterface|iterable $responses, float $timeout = n return $this->client->stream($responses, $timeout); } + /** + * @return void + */ public function reset() { if ($this->client instanceof ResetInterface) { diff --git a/src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php b/src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php index 892c3e4d2ce96..e8d699e9993ce 100644 --- a/src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php +++ b/src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php @@ -235,8 +235,8 @@ public function testBufferPurePassthru() public function testRetryTimeout() { - $cpt = 0; - $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) use (&$cpt) { + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) { + static $cpt = 0; try { $this->assertTrue($chunk->isTimeout()); yield $chunk; @@ -301,8 +301,8 @@ public function testInfoPassToDecorator() public function testMultipleYieldInInitializer() { - $first = null; - $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) use (&$first) { + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) { + static $first; if ($chunk->isFirst()) { $first = $chunk; @@ -343,8 +343,8 @@ public function request(string $method, string $url, array $options = []): Respo public function testMaxDuration() { - $sawFirst = false; - $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) use (&$sawFirst) { + $client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) { + static $sawFirst = false; try { if (!$chunk->isFirst() || !$sawFirst) { $sawFirst = $sawFirst || $chunk->isFirst(); diff --git a/src/Symfony/Component/HttpClient/Tests/Chunk/ServerSentEventTest.php b/src/Symfony/Component/HttpClient/Tests/Chunk/ServerSentEventTest.php index 1c0d6834a7272..595dd3342afa1 100644 --- a/src/Symfony/Component/HttpClient/Tests/Chunk/ServerSentEventTest.php +++ b/src/Symfony/Component/HttpClient/Tests/Chunk/ServerSentEventTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\Chunk\ServerSentEvent; +use Symfony\Component\HttpClient\Exception\JsonException; /** * @author Antoine Bluchet @@ -76,4 +77,45 @@ public function testParseNewLine() $sse = new ServerSentEvent($rawData); $this->assertSame("\n\n \n\n\n", $sse->getData()); } + + public function testGetArrayData() + { + $this->assertSame(['foo' => 'bar'], (new ServerSentEvent(<<getArrayData()); + } + + public function testGetArrayDataWithNoContent() + { + $this->expectException(JsonException::class); + $this->expectExceptionMessage('Server-Sent Event data is empty.'); + + (new ServerSentEvent(''))->getArrayData(); + } + + public function testGetArrayDataWithInvalidJson() + { + $this->expectException(JsonException::class); + $this->expectExceptionMessage('Decoding Server-Sent Event "33" failed: Syntax error'); + + (new ServerSentEvent(<<getArrayData(); + } + + public function testGetArrayDataWithNonArrayJson() + { + $this->expectException(JsonException::class); + $this->expectExceptionMessage('JSON content was expected to decode to an array, "string" returned in Server-Sent Event "33".'); + + (new ServerSentEvent(<<getArrayData(); + } } diff --git a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php index 7641d54164d58..7a9f22cab1e9e 100755 --- a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php +++ b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php @@ -193,7 +193,7 @@ public static function provideCurlRequests(): iterable --url %1$shttp://localhost:8057/json%1$s \\ --header %1$sAccept: */*%1$s \\ --header %1$sAccept-Encoding: gzip%1$s \\ - --header %1$sUser-Agent: Symfony HttpClient/Native%1$s', + --header %1$sUser-Agent: Symfony HttpClient (Native)%1$s', ]; yield 'GET with base uri' => [ [ @@ -209,7 +209,7 @@ public static function provideCurlRequests(): iterable --url %1$shttp://localhost:8057/json/1%1$s \\ --header %1$sAccept: */*%1$s \\ --header %1$sAccept-Encoding: gzip%1$s \\ - --header %1$sUser-Agent: Symfony HttpClient/Native%1$s', + --header %1$sUser-Agent: Symfony HttpClient (Native)%1$s', ]; yield 'GET with resolve' => [ [ @@ -229,7 +229,7 @@ public static function provideCurlRequests(): iterable --url %1$shttp://localhost:8057/json%1$s \\ --header %1$sAccept: */*%1$s \\ --header %1$sAccept-Encoding: gzip%1$s \\ - --header %1$sUser-Agent: Symfony HttpClient/Native%1$s', + --header %1$sUser-Agent: Symfony HttpClient (Native)%1$s', ]; yield 'POST with string body' => [ [ @@ -247,7 +247,7 @@ public static function provideCurlRequests(): iterable --header %1$sContent-Length: 9%1$s \\ --header %1$sContent-Type: application/x-www-form-urlencoded%1$s \\ --header %1$sAccept-Encoding: gzip%1$s \\ - --header %1$sUser-Agent: Symfony HttpClient/Native%1$s \\ + --header %1$sUser-Agent: Symfony HttpClient (Native)%1$s \\ --data %1$sfoobarbaz%1$s', ]; yield 'POST with array body' => [ @@ -285,7 +285,7 @@ public function __toString(): string --header %1$sContent-Type: application/x-www-form-urlencoded%1$s \\ --header %1$sContent-Length: 211%1$s \\ --header %1$sAccept-Encoding: gzip%1$s \\ - --header %1$sUser-Agent: Symfony HttpClient/Native%1$s \\ + --header %1$sUser-Agent: Symfony HttpClient (Native)%1$s \\ --data %1$sfoo=fooval%1$s --data %1$sbar=barval%1$s --data %1$sbaz=bazval%1$s --data %1$sfoobar[baz]=bazval%1$s --data %1$sfoobar[qux]=quxval%1$s --data %1$sbazqux[0]=bazquxval1%1$s --data %1$sbazqux[1]=bazquxval2%1$s --data %1$sobject[fooprop]=foopropval%1$s --data %1$sobject[barprop]=barpropval%1$s --data %1$stostring=tostringval%1$s', ]; @@ -312,7 +312,7 @@ public function __toString(): string --url %1$shttp://localhost:8057/?foo=fooval&bar=newbarval&foobar[baz]=bazval&foobar[qux]=quxval&bazqux[0]=bazquxval1&bazqux[1]=bazquxval2%1$s \\ --header %1$sAccept: */*%1$s \\ --header %1$sAccept-Encoding: gzip%1$s \\ - --header %1$sUser-Agent: Symfony HttpClient/Native%1$s', + --header %1$sUser-Agent: Symfony HttpClient (Native)%1$s', ]; yield 'POST with json' => [ [ @@ -336,7 +336,7 @@ public function __toString(): string --header %1$sAccept: */*%1$s \\ --header %1$sContent-Length: 120%1$s \\ --header %1$sAccept-Encoding: gzip%1$s \\ - --header %1$sUser-Agent: Symfony HttpClient/Native%1$s \\ + --header %1$sUser-Agent: Symfony HttpClient (Native)%1$s \\ --data %1$s{"foo":{"bar":"baz","qux":[1.1,1.0],"fred":["\u003Cfoo\u003E","\u0027bar\u0027","\u0022baz\u0022","\u0026blong\u0026"]}}%1$s', ]; } @@ -368,7 +368,7 @@ public function testItDoesNotFollowRedirectionsWhenGeneratingCurlCommands() --header %1$sAccept: */*%1$s \\ --header %1$sAuthorization: Basic Zm9vOmJhcg==%1$s \\ --header %1$sAccept-Encoding: gzip%1$s \\ - --header %1$sUser-Agent: Symfony HttpClient/Native%1$s', '\\' === \DIRECTORY_SEPARATOR ? '"' : "'"), $curlCommand + --header %1$sUser-Agent: Symfony HttpClient (Native)%1$s', '\\' === \DIRECTORY_SEPARATOR ? '"' : "'"), $curlCommand ); } diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php index 1263137edb934..4d083bc27a9e9 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php @@ -321,10 +321,6 @@ private static function startVulcain(HttpClientInterface $client) throw new SkippedTestSuiteError('Testing with the "vulcain" is not supported on Windows.'); } - if (['application/json'] !== $client->request('GET', 'http://127.0.0.1:8057/json')->getHeaders()['content-type']) { - throw new SkippedTestSuiteError('symfony/http-client-contracts >= 2.0.1 required'); - } - $process = new Process(['vulcain'], null, [ 'DEBUG' => 1, 'UPSTREAM' => 'http://127.0.0.1:8057', diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php index 3d8d7559d9bf4..9453297d7dc8b 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\HttpClientTrait; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -68,6 +69,96 @@ public function testPrepareRequestWithBodyIsArray() $this->assertContains('Content-Type: application/x-www-form-urlencoded; charset=utf-8', $options['headers']); } + public function testNormalizeBodyMultipart() + { + $file = fopen('php://memory', 'r+'); + stream_context_set_option($file, ['http' => [ + 'filename' => 'test.txt', + 'content_type' => 'text/plain', + ]]); + fwrite($file, 'foobarbaz'); + rewind($file); + + $headers = [ + 'content-type' => ['Content-Type: multipart/form-data; boundary=ABCDEF'], + ]; + $body = [ + 'foo[]' => 'bar', + 'bar' => [ + $file, + ], + ]; + + $body = self::normalizeBody($body, $headers); + + $result = ''; + while ('' !== $data = $body(self::$CHUNK_SIZE)) { + $result .= $data; + } + + $expected = <<<'EOF' + --ABCDEF + Content-Disposition: form-data; name="foo[]" + + bar + --ABCDEF + Content-Disposition: form-data; name="bar[0]"; filename="test.txt" + Content-Type: text/plain + + foobarbaz + --ABCDEF-- + + EOF; + $expected = str_replace("\n", "\r\n", $expected); + + $this->assertSame($expected, $result); + } + + /** + * @group network + * + * @requires extension openssl + * + * @dataProvider provideNormalizeBodyMultipartForwardStream + */ + public function testNormalizeBodyMultipartForwardStream($stream) + { + $body = [ + 'logo' => $stream, + ]; + + $headers = []; + $body = self::normalizeBody($body, $headers); + + $result = ''; + while ('' !== $data = $body(self::$CHUNK_SIZE)) { + $result .= $data; + } + + $this->assertSame(1, preg_match('/^Content-Type: multipart\/form-data; boundary=(?.+)$/', $headers['content-type'][0], $matches)); + $this->assertSame('Content-Length: 3086', $headers['content-length'][0]); + $this->assertSame(3086, \strlen($result)); + + $expected = <<assertStringMatchesFormat($expected, $result); + } + + public static function provideNormalizeBodyMultipartForwardStream() + { + yield 'native' => [fopen('https://github.githubassets.com/images/icons/emoji/unicode/1f44d.png', 'r')]; + yield 'symfony' => [HttpClient::create()->request('GET', 'https://github.githubassets.com/images/icons/emoji/unicode/1f44d.png')->toStream()]; + } + /** * @dataProvider provideResolveUrl */ diff --git a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php index 23dc7b6d9f0b0..7d5d6464a81c0 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php @@ -204,7 +204,7 @@ public function testRetryNetworkError() return $response; }, function (\Exception $exception) use (&$failureCallableCalled, $client) { $this->assertSame(NetworkException::class, $exception::class); - $this->assertSame(TransportException::class, \get_class($exception->getPrevious())); + $this->assertSame(TransportException::class, $exception->getPrevious()::class); $failureCallableCalled = true; 10000 return $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057')); @@ -251,11 +251,7 @@ function (\Exception $exception) use ($errorMessage, &$failureCallableCalled, $c $failureCallableCalled = true; // Ensure arbitrary levels of promises work. - return (new FulfilledPromise(null))->then(function () use ($client, $request) { - return (new GuzzleFulfilledPromise(null))->then(function () use ($client, $request) { - return $client->sendAsyncRequest($request); - }); - }); + return (new FulfilledPromise(null))->then(fn () => (new GuzzleFulfilledPromise(null))->then(fn () => $client->sendAsyncRequest($request))); } ) ; diff --git a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php index fc0d1852e12b1..6da3af6bca9dd 100644 --- a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php @@ -45,9 +45,7 @@ public function testMocking($factory, array $expectedResponses) public static function mockingProvider(): iterable { yield 'callable' => [ - static function (string $method, string $url, array $options = []) { - return new MockResponse($method.': '.$url.' (body='.$options['body'].')'); - }, + static fn (string $method, string $url, array $options = []) => new MockResponse($method.': '.$url.' (body='.$options['body'].')'), [ 'POST: https://example.com/foo (body=payload)', 'POST: https://example.com/bar (body=payload)', @@ -56,12 +54,8 @@ static function (string $method, string $url, array $options = []) { yield 'array of callable' => [ [ - static function (string $method, string $url, array $options = []) { - return new MockResponse($method.': '.$url.' (body='.$options['body'].') [1]'); - }, - static function (string $method, string $url, array $options = []) { - return new MockResponse($method.': '.$url.' (body='.$options['body'].') [2]'); - }, + static fn (string $method, string $url, array $options = []) => new MockResponse($method.': '.$url.' (body='.$options['body'].') [1]'), + static fn (string $method, string $url, array $options = []) => new MockResponse($method.': '.$url.' (body='.$options['body'].') [2]'), ], [ 'POST: https://example.com/foo (body=payload) [1]', @@ -115,7 +109,7 @@ public function testValidResponseFactory($responseFactory) public static function validResponseFactoryProvider() { return [ - [static function (): MockResponse { return new MockResponse(); }], + [static fn (): MockResponse => new MockResponse()], [new MockResponse()], [[new MockResponse()]], [new \ArrayIterator([new MockResponse()])], @@ -142,12 +136,8 @@ public static function transportExceptionProvider(): iterable { yield 'array of callable' => [ [ - static function (string $method, string $url, array $options = []) { - return new MockResponse(); - }, - static function (string $method, string $url, array $options = []) { - return new MockResponse(); - }, + static fn (string $method, string $url, array $options = []) => new MockResponse(), + static fn (string $method, string $url, array $options = []) => new MockResponse(), ], ]; @@ -183,7 +173,7 @@ public static function invalidResponseFactoryProvider() { return [ [static function (): \Generator { yield new MockResponse(); }, 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "Generator" given.'], - [static function (): array { return [new MockResponse()]; }, 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "array" given.'], + [static fn (): array => [new MockResponse()], 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "array" given.'], [(static function (): \Generator { yield 'ccc'; })(), 'The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "string" given.'], ]; } @@ -564,4 +554,23 @@ public function testCancelingMockResponseExecutesOnProgressWithUpdatedInfo() $this->assertTrue($canceled); } + + public function testEmptyResponseFactory() + { + $this->expectException(TransportException::class); + $this->expectExceptionMessage('The response factory iterator passed to MockHttpClient is empty.'); + + $client = new MockHttpClient([]); + $client->request('GET', 'https://example.com'); + } + + public function testMoreRequestsThanResponseFactoryResponses() + { + $this->expectException(TransportException::class); + $this->expectExceptionMessage('No more response left in the response factory iterator passed to MockHttpClient: the number of requests exceeds the number of responses.'); + + $client = new MockHttpClient([new MockResponse()]); + $client->request('GET', 'https://example.com'); + $client->request('GET', 'https://example.com'); + } } diff --git a/src/Symfony/Component/HttpClient/Tests/Response/HttplugPromiseTest.php b/src/Symfony/Component/HttpClient/Tests/Response/HttplugPromiseTest.php index d781d4925b17b..9fb3ad2fc9631 100644 --- a/src/Symfony/Component/HttpClient/Tests/Response/HttplugPromiseTest.php +++ b/src/Symfony/Component/HttpClient/Tests/Response/HttplugPromiseTest.php @@ -29,7 +29,7 @@ public function testComplexNesting() $promise1 = $mkPromise('result'); $promise2 = $promise1->then($mkPromise); - $promise3 = $promise2->then(function ($result) { return $result; }); + $promise3 = $promise2->then(fn ($result) => $result); $this->assertSame('result', $promise3->wait()); } diff --git a/src/Symfony/Component/HttpClient/Tests/Response/JsonMockResponseTest.php b/src/Symfony/Component/HttpClient/Tests/Response/JsonMockResponseTest.php new file mode 100644 index 0000000000000..b371c08cf4241 --- /dev/null +++ b/src/Symfony/Component/HttpClient/Tests/Response/JsonMockResponseTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests\Response; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\JsonMockResponse; + +final class JsonMockResponseTest extends TestCase +{ + public function testDefaults() + { + $client = new MockHttpClient(new JsonMockResponse()); + $response = $client->request('GET', 'https://symfony.com'); + + $this->assertSame([], $response->toArray()); + $this->assertSame('application/json', $response->getHeaders()['content-type'][0]); + } + + public function testInvalidBody() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('JSON encoding failed: Malformed UTF-8 characters, possibly incorrectly encoded'); + + new JsonMockResponse("\xB1\x31"); + } + + public function testJsonEncodeArray() + { + $client = new MockHttpClient(new JsonMockResponse([ + 'foo' => 'bar', + 'ccc' => 123, + ])); + $response = $client->request('GET', 'https://symfony.com'); + + $this->assertSame([ + 'foo' => 'bar', + 'ccc' => 123, + ], $response->toArray()); + $this->assertSame('application/json', $response->getHeaders()['content-type'][0]); + } + + public function testJsonEncodeString() + { + $client = new MockHttpClient(new JsonMockResponse('foobarccc')); + $response = $client->request('GET', 'https://symfony.com'); + + $this->assertSame('"foobarccc"', $response->getContent()); + $this->assertSame('application/json', $response->getHeaders()['content-type'][0]); + } + + /** + * @dataProvider responseHeadersProvider + */ + public function testResponseHeaders(string $expectedContentType, array $responseHeaders) + { + $client = new MockHttpClient(new JsonMockResponse([ + 'foo' => 'bar', + ], [ + 'response_headers' => $responseHeaders, + 'http_code' => 201, + ])); + $response = $client->request('GET', 'https://symfony.com'); + + $this->assertSame($expectedContentType, $response->getHeaders()['content-type'][0]); + $this->assertSame(201, $response->getStatusCode()); + } + + public static function responseHeadersProvider(): array + { + return [ + ['application/json', []], + ['application/json', ['x-foo' => 'ccc']], + ['application/problem+json', ['content-type' => 'application/problem+json']], + ['application/problem+json', ['x-foo' => 'ccc', 'content-type' => 'application/problem+json']], + ]; + } +} diff --git a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php index cf2af1560c345..b32601aefc5a5 100644 --- a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php @@ -244,4 +244,104 @@ public function testRetryOnErrorAssertContent() self::assertSame('Test out content', $response->getContent()); self::assertSame('Test out content', $response->getContent(), 'Content should be buffered'); } + + public function testRetryWithMultipleBaseUris() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + new MockResponse('Hit on second uri', ['http_code' => 200]), + ]), + new GenericRetryStrategy([500], 0), + 1 + ); + + $response = $client->request('GET', 'foo-bar', [ + 'base_uri' => [ + 'http://example.com/a/', + 'http://example.com/b/', + ], + ]); + + self::assertSame(200, $response->getStatusCode()); + self::assertSame('http://example.com/b/foo-bar', $response->getInfo('url')); + } + + public function testMultipleBaseUrisAsOptions() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + new MockResponse('Hit on second uri', ['http_code' => 200]), + ]), + new GenericRetryStrategy([500], 0), + 1 + ); + + $client = $client->withOptions([ + 'base_uri' => [ + 'http://example.com/a/', + 'http://example.com/b/', + ], + ]); + + $response = $client->request('GET', 'foo-bar'); + + self::assertSame(200, $response->getStatusCode()); + self::assertSame('http://example.com/b/foo-bar', $response->getInfo('url')); + } + + public function testRetryWithMultipleBaseUrisShufflesNestedArray() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + new MockResponse('Hit on second uri', ['http_code' => 200]), + ]), + new GenericRetryStrategy([500], 0), + 1 + ); + + $response = $client->request('GET', 'foo-bar', [ + 'base_uri' => [ + 'http://example.com/a/', + [ + 'http://example.com/b/', + 'http://example.com/c/', + ], + 'http://example.com/d/', + ], + ]); + + self::assertSame(200, $response->getStatusCode()); + self::assertMatchesRegularExpression('#^http://example.com/(b|c)/foo-bar$#', $response->getInfo('url')); + } + + public function testRetryWithMultipleBaseUrisPreservesNonNestedOrder() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + new MockResponse('', ['http_code' => 500]), + new MockResponse('', ['http_code' => 500]), + new MockResponse('Hit on second uri', ['http_code' => 200]), + ]), + new GenericRetryStrategy([500], 0), + 3 + ); + + $response = $client->request('GET', 'foo-bar', [ + 'base_uri' => [ + 'http://example.com/a/', + [ + 'http://example.com/b/', + 'http://example.com/c/', + ], + 'http://example.com/d/', + ], + ]); + + self::assertSame(200, $response->getStatusCode()); + self::assertSame('http://example.com/d/foo-bar', $response->getInfo('url')); + } } diff --git a/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php index 5f20e1989dfa1..cf437a653bd76 100755 --- a/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php @@ -123,9 +123,7 @@ public function testToArrayChecksStatusCodeBeforeDecoding() { $this->expectException(ClientExceptionInterface::class); - $sut = new TraceableHttpClient(new MockHttpClient($responseFactory = function (): MockResponse { - return new MockResponse('Errored.', ['http_code' => 400]); - })); + $sut = new TraceableHttpClient(new MockHttpClient($responseFactory = fn (): MockResponse => new MockResponse('Errored.', ['http_code' => 400]))); $response = $sut->request('GET', 'https://example.com/foo/bar'); $response->toArray(); diff --git a/src/Symfony/Component/HttpClient/Tests/UriTemplateHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/UriTemplateHttpClientTest.php new file mode 100644 index 0000000000000..23e48b50d8c03 --- /dev/null +++ b/src/Symfony/Component/HttpClient/Tests/UriTemplateHttpClientTest.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\UriTemplateHttpClient; + +final class UriTemplateHttpClientTest extends TestCase +{ + public function testExpanderIsCalled() + { + $client = new UriTemplateHttpClient( + new MockHttpClient(), + function (string $url, array $vars): string { + $this->assertSame('https://foo.tld/{version}/{resource}{?page}', $url); + $this->assertSame([ + 'version' => 'v2', + 'resource' => 'users', + 'page' => 33, + ], $vars); + + return 'https://foo.tld/v2/users?page=33'; + }, + [ + 'version' => 'v2', + ], + ); + $this->assertSame('https://foo.tld/v2/users?page=33', $client->request('GET', 'https://foo.tld/{version}/{resource}{?page}', [ + 'vars' => [ + 'resource' => 'users', + 'page' => 33, + ], + ])->getInfo('url')); + } + + public function testWithOptionsAppendsVarsToDefaultVars() + { + $client = new UriTemplateHttpClient( + new MockHttpClient(), + function (string $url, array $vars): string { + $this->assertSame('https://foo.tld/{bar}', $url); + $this->assertSame([ + 'bar' => 'ccc', + ], $vars); + + return 'https://foo.tld/ccc'; + }, + ); + $this->assertSame('https://foo.tld/{bar}', $client->request('GET', 'https://foo.tld/{bar}')->getInfo('url')); + + $client = $client->withOptions([ + 'vars' => [ + 'bar' => 'ccc', + ], + ]); + $this->assertSame('https://foo.tld/ccc', $client->request('GET', 'https://foo.tld/{bar}')->getInfo('url')); + } + + public function testExpanderIsNotCalledWithEmptyVars() + { + $this->expectNotToPerformAssertions(); + + $client = new UriTemplateHttpClient(new MockHttpClient(), $this->fail(...)); + $client->request('GET', 'https://foo.tld/bar', [ + 'vars' => [], + ]); + } + + public function testExpanderIsNotCalledWithNoVarsAtAll() + { + $this->expectNotToPerformAssertions(); + + $client = new UriTemplateHttpClient(new MockHttpClient(), $this->fail(...)); + $client->request('GET', 'https://foo.tld/bar'); + } + + public function testRequestWithNonArrayVarsOption() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The "vars" option must be an array.'); + + (new UriTemplateHttpClient(new MockHttpClient()))->request('GET', 'https://foo.tld', [ + 'vars' => 'should be an array', + ]); + } + + public function testWithOptionsWithNonArrayVarsOption() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The "vars" option must be an array.'); + + (new UriTemplateHttpClient(new MockHttpClient()))->withOptions([ + 'vars' => new \stdClass(), + ]); + } + + public function testVarsOptionIsNotPropagated() + { + $client = new UriTemplateHttpClient( + new MockHttpClient(function (string $method, string $url, array $options): MockResponse { + $this->assertArrayNotHasKey('vars', $options); + + return new MockResponse(); + }), + static fn (): string => 'ccc', + ); + + $client->withOptions([ + 'vars' => [ + 'foo' => 'bar', + ], + ])->request('GET', 'https://foo.tld', [ + 'vars' => [ + 'foo2' => 'bar2', + ], + ]); + } +} diff --git a/src/Symfony/Component/HttpClient/TraceableHttpClient.php b/src/Symfony/Component/HttpClient/TraceableHttpClient.php index b9df9732aa9dd..974e9f6f00646 100644 --- a/src/Symfony/Component/HttpClient/TraceableHttpClient.php +++ b/src/Symfony/Component/HttpClient/TraceableHttpClient.php @@ -80,7 +80,7 @@ public function getTracedRequests(): array return $this->tracedRequests->getArrayCopy(); } - public function reset() + public function reset(): void { if ($this->client instanceof ResetInterface) { $this->client->reset(); diff --git a/src/Symfony/Component/HttpClient/UriTemplateHttpClient.php b/src/Symfony/Component/HttpClient/UriTemplateHttpClient.php new file mode 100644 index 0000000000000..55ae724f12207 --- /dev/null +++ b/src/Symfony/Component/HttpClient/UriTemplateHttpClient.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +class UriTemplateHttpClient implements HttpClientInterface, ResetInterface +{ + use DecoratorTrait; + + /** + * @param (\Closure(string $url, array $vars): string)|null $expander + */ + public function __construct(HttpClientInterface $client = null, private ?\Closure $expander = null, private array $defaultVars = []) + { + $this->client = $client ?? HttpClient::create(); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $vars = $this->defaultVars; + + if (\array_key_exists('vars', $options)) { + if (!\is_array($options['vars'])) { + throw new \InvalidArgumentException('The "vars" option must be an array.'); + } + + $vars = [...$vars, ...$options['vars']]; + unset($options['vars']); + } + + if ($vars) { + $url = ($this->expander ??= $this->createExpanderFromPopularVendors())($url, $vars); + } + + return $this->client->request($method, $url, $options); + } + + public function withOptions(array $options): static + { + if (!\is_array($options['vars'] ?? [])) { + throw new \InvalidArgumentException('The "vars" option must be an array.'); + } + + $clone = clone $this; + $clone->defaultVars = [...$clone->defaultVars, ...$options['vars'] ?? []]; + unset($options['vars']); + + $clone->client = $this->client->withOptions($options); + + return $clone; + } + + /** + * @return \Closure(string $url, array $vars): string + */ + private function createExpanderFromPopularVendors(): \Closure + { + if (class_exists(\GuzzleHttp\UriTemplate\UriTemplate::class)) { + return \GuzzleHttp\UriTemplate\UriTemplate::expand(...); + } + + if (class_exists(\League\Uri\UriTemplate::class)) { + return static fn (string $url, array $vars): string => (new \League\Uri\UriTemplate($url))->expand($vars); + } + + if (class_exists(\Rize\UriTemplate::class)) { + return (new \Rize\UriTemplate())->expand(...); + } + + throw new \LogicException('Support for URI template requires a vendor to expand the URI. Run "composer require guzzlehttp/uri-template" or pass your own expander \Closure implementation.'); + } +} diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index 1e9adde053093..79740066dd7ab 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -24,9 +24,9 @@ "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "^3", - "symfony/service-contracts": "^1.0|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "require-dev": { "amphp/amp": "^2.5", @@ -42,6 +42,9 @@ "symfony/process": "^5.4|^6.0", "symfony/stopwatch": "^5.4|^6.0" }, + "conflict": { + "symfony/http-foundation": "<6.3" + }, "autoload": { "psr-4": { "Symfony\\Component\\HttpClient\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/HttpFoundation/AcceptHeader.php b/src/Symfony/Component/HttpFoundation/AcceptHeader.php index 180e9604c7fad..853c000e00f12 100644 --- a/src/Symfony/Component/HttpFoundation/AcceptHeader.php +++ b/src/Symfony/Component/HttpFoundation/AcceptHeader.php @@ -46,11 +46,10 @@ public function __construct(array $items) */ public static function fromString(?string $headerValue): self { - $index = 0; - $parts = HeaderUtils::split($headerValue ?? '', ',;='); - return new self(array_map(function ($subParts) use (&$index) { + return new self(array_map(function ($subParts) { + static $index = 0; $part = array_shift($subParts); $attributes = HeaderUtils::combine($subParts); @@ -115,9 +114,7 @@ public function all(): array */ public function filter(string $pattern): self { - return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { - return preg_match($pattern, $item->getValue()); - })); + return new self(array_filter($this->items, fn (AcceptHeaderItem $item) => preg_match($pattern, $item->getValue()))); } /** diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index 72cef7c0ea114..d29b1a34e7a5f 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -358,6 +358,8 @@ public function getContent(): string|false /** * Trust X-Sendfile-Type header. + * + * @return void */ public static function trustXSendfileTypeHeader() { diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index fdea3d67e1b19..a98d23d9d1094 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -1,9 +1,24 @@ CHANGELOG ========= +6.3 +--- + + * Calling `ParameterBag::getDigit()`, `getAlnum()`, `getAlpha()` on an `array` throws a `UnexpectedValueException` instead of a `TypeError` + * Add `ParameterBag::getString()` to convert a parameter into string and throw an exception if the value is invalid + * Add `ParameterBag::getEnum()` + * Create migration for session table when pdo handler is used + * Add support for Relay PHP extension for Redis + * The `Response::sendHeaders()` method now takes an optional HTTP status code as parameter, allowing to send informational responses such as Early Hints responses (103 status code) + * Add `IpUtils::isPrivateIp()` + * Add `Request::getPayload(): InputBag` + * Deprecate conversion of invalid values in `ParameterBag::getInt()` and `ParameterBag::getBoolean()`, + * Deprecate ignoring invalid values when using `ParameterBag::filter()`, unless flag `FILTER_NULL_ON_FAILURE` is set + 6.2 --- + * Add `StreamedJsonResponse` class for efficient JSON streaming * The HTTP cache store uses the `xxh128` algorithm * Deprecate calling `JsonResponse::setCallback()`, `Response::setExpires/setLastModified/setEtag()`, `MockArraySessionStorage/NativeSessionStorage::setMetadataBag()`, `NativeSessionStorage::setSaveHandler()` without arguments * Add request matchers under the `Symfony\Component\HttpFoundation\RequestMatcher` namespace diff --git a/src/Symfony/Component/HttpFoundation/ExpressionRequestMatcher.php b/src/Symfony/Component/HttpFoundation/ExpressionRequestMatcher.php index 5628ea8bc14de..fe65e920d92c0 100644 --- a/src/Symfony/Component/HttpFoundation/ExpressionRequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/ExpressionRequestMatcher.php @@ -29,6 +29,9 @@ class ExpressionRequestMatcher extends RequestMatcher private ExpressionLanguage $language; private Expression|string $expression; + /** + * @return void + */ public function setExpression(ExpressionLanguage $language, Expression|string $expression) { $this->language = $language; @@ -38,7 +41,7 @@ public function setExpression(ExpressionLanguage $language, Expression|string $e public function matches(Request $request): bool { if (!isset($this->language)) { - throw new \LogicException('Unable to match the request as the expression language is not available.'); + throw new \LogicException('Unable to match the request as the expression language is not available. Try running "composer require symfony/expression-language".'); } return $this->language->evaluate($this->expression, [ diff --git a/src/Symfony/Component/HttpFoundation/FileBag.php b/src/Symfony/Component/HttpFoundation/FileBag.php index 7ed39408fd5af..b74a02e2e1e53 100644 --- a/src/Symfony/Component/HttpFoundation/FileBag.php +++ b/src/Symfony/Component/HttpFoundation/FileBag.php @@ -31,12 +31,18 @@ public function __construct(array $parameters = []) $this->replace($parameters); } + /** + * @return void + */ public function replace(array $files = []) { $this->parameters = []; $this->add($files); } + /** + * @return void + */ public function set(string $key, mixed $value) { if (!\is_array($value) && !$value instanceof UploadedFile) { @@ -46,6 +52,9 @@ public function set(string $key, mixed $value) parent::set($key, $this->convertFileInformation($value)); } + /** + * @return void + */ public function add(array $files = []) { foreach ($files as $key => $file) { @@ -75,7 +84,7 @@ protected function convertFileInformation(array|UploadedFile $file): array|Uploa $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error'], false); } } else { - $file = array_map(function ($v) { return $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v; }, $file); + $file = array_map(fn ($v) => $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v, $file); if (array_keys($keys) === $keys) { $file = array_filter($file); } diff --git a/src/Symfony/Component/HttpFoundation/HeaderBag.php b/src/Symfony/Component/HttpFoundation/HeaderBag.php index 0883024b3b50b..081f26a2d095c 100644 --- a/src/Symfony/Component/HttpFoundation/HeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/HeaderBag.php @@ -63,7 +63,7 @@ public function __toString(): string * * @param string|null $key The name of the headers to return or null to get them all * - * @return array>|array + * @return ($key is null ? array> : array) */ public function all(string $key = null): array { @@ -86,6 +86,8 @@ public function keys(): array /** * Replaces the current HTTP headers by a new set. + * + * @return void */ public function replace(array $headers = []) { @@ -95,6 +97,8 @@ public function replace(array $headers = []) /** * Adds new headers the current HTTP headers set. + * + * @return void */ public function add(array $headers) { @@ -126,6 +130,8 @@ public function get(string $key, string $default = null): ?string * * @param string|string[]|null $values The value or an array of values * @param bool $replace Whether to replace the actual value or not (true by default) + * + * @return void */ public function set(string $key, string|array|null $values, bool $replace = true) { @@ -170,6 +176,8 @@ public function contains(string $key, string $value): bool /** * Removes a header. + * + * @return void */ public function remove(string $key) { @@ -202,6 +210,8 @@ public function getDate(string $key, \DateTime $default = null): ?\DateTimeInter /** * Adds a custom Cache-Control directive. + * + * @return void */ public function addCacheControlDirective(string $key, bool|string $value = true) { @@ -228,6 +238,8 @@ public function getCacheControlDirective(string $key): bool|string|null /** * Removes a Cache-Control directive. + * + * @return void */ public function removeCacheControlDirective(string $key) { @@ -254,6 +266,9 @@ public function count(): int return \count($this->headers); } + /** + * @return string + */ protected function getCacheControlHeader() { ksort($this->cacheControl); diff --git a/src/Symfony/Component/HttpFoundation/InputBag.php b/src/Symfony/Component/HttpFoundation/InputBag.php index 877ac60f3aefd..77990f5711ece 100644 --- a/src/Symfony/Component/HttpFoundation/InputBag.php +++ b/src/Symfony/Component/HttpFoundation/InputBag.php @@ -43,7 +43,7 @@ public function get(string $key, mixed $default = null): string|int|float|bool|n /** * Replaces the current input values by a new set. */ - public function replace(array $inputs = []) + public function replace(array $inputs = []): void { $this->parameters = []; $this->add($inputs); @@ -52,7 +52,7 @@ public function replace(array $inputs = []) /** * Adds input values. */ - public function add(array $inputs = []) + public function add(array $inputs = []): void { foreach ($inputs as $input => $value) { $this->set($input, $value); @@ -64,7 +64,7 @@ public function add(array $inputs = []) * * @param string|int|float|bool|array|null $value */ - public function set(string $key, mixed $value) + public function set(string $key, mixed $value): void { if (null !== $value && !\is_scalar($value) && !\is_array($value) && !$value instanceof \Stringable) { throw new \InvalidArgumentException(sprintf('Expected a scalar, or an array as a 2nd argument to "%s()", "%s" given.', __METHOD__, get_debug_type($value))); @@ -73,6 +73,34 @@ public function set(string $key, mixed $value) $this->parameters[$key] = $value; } + /** + * Returns the parameter value converted to an enum. + * + * @template T of \BackedEnum + * + * @param class-string $class + * @param ?T $default + * + * @return ?T + */ + public function getEnum(string $key, string $class, \BackedEnum $default = null): ?\BackedEnum + { + try { + return parent::getEnum($key, $class, $default); + } catch (\UnexpectedValueException $e) { + throw new BadRequestException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns the parameter value converted to string. + */ + public function getString(string $key, string $default = ''): string + { + // Shortcuts the parent method because the validation on scalar is already done in get(). + return (string) $this->get($key, $default); + } + public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed { $value = $this->has($key) ? $this->all()[$key] : $default; @@ -90,6 +118,22 @@ public function filter(string $key, mixed $default = null, int $filter = \FILTER throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); } - return filter_var($value, $filter, $options); + $options['flags'] ??= 0; + $nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE; + $options['flags'] |= \FILTER_NULL_ON_FAILURE; + + $value = filter_var($value, $filter, $options); + + if (null !== $value || $nullOnFailure) { + return $value; + } + + $method = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)[1]; + $method = ($method['object'] ?? null) === $this ? $method['function'] : 'filter'; + $hint = 'filter' === $method ? 'pass' : 'use method "filter()" with'; + + trigger_deprecation('symfony/http-foundation', '6.3', 'Ignoring invalid values when using "%s::%s(\'%s\')" is deprecated and will throw a "%s" in 7.0; '.$hint.' flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', $this::class, $method, $key, BadRequestException::class); + + return false; } } diff --git a/src/Symfony/Component/HttpFoundation/IpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php index 917eb6e1a455b..ceab620c2f560 100644 --- a/src/Symfony/Component/HttpFoundation/IpUtils.php +++ b/src/Symfony/Component/HttpFoundation/IpUtils.php @@ -18,6 +18,21 @@ */ class IpUtils { + public const PRIVATE_SUBNETS = [ + '127.0.0.0/8', // RFC1700 (Loopback) + '10.0.0.0/8', // RFC1918 + '192.168.0.0/16', // RFC1918 + '172.16.0.0/12', // RFC1918 + '169.254.0.0/16', // RFC3927 + '0.0.0.0/8', // RFC5735 + '240.0.0.0/4', // RFC1112 + '::1/128', // Loopback + 'fc00::/7', // Unique Local Address + 'fe80::/10', // Link Local Address + '::ffff:0:0/96', // IPv4 translations + '::/128', // Unspecified address + ]; + private static array $checkedIps = []; /** @@ -60,23 +75,23 @@ public static function checkIp(string $requestIp, string|array $ips): bool public static function checkIp4(string $requestIp, string $ip): bool { $cacheKey = $requestIp.'-'.$ip.'-v4'; - if (isset(self::$checkedIps[$cacheKey])) { - return self::$checkedIps[$cacheKey]; + if (null !== $cacheValue = self::getCacheResult($cacheKey)) { + return $cacheValue; } if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } if (str_contains($ip, '/')) { [$address, $netmask] = explode('/', $ip, 2); if ('0' === $netmask) { - return self::$checkedIps[$cacheKey] = false !== filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4); + return self::setCacheResult($cacheKey, false !== filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)); } if ($netmask < 0 || $netmask > 32) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } } else { $address = $ip; @@ -84,10 +99,10 @@ public static function checkIp4(string $requestIp, string $ip): bool } if (false === ip2long($address)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } - return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + return self::setCacheResult($cacheKey, 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask)); } /** @@ -105,8 +120,8 @@ public static function checkIp4(string $requestIp, string $ip): bool public static function checkIp6(string $requestIp, string $ip): bool { $cacheKey = $requestIp.'-'.$ip.'-v6'; - if (isset(self::$checkedIps[$cacheKey])) { - return self::$checkedIps[$cacheKey]; + if (null !== $cacheValue = self::getCacheResult($cacheKey)) { + return $cacheValue; } if (!((\extension_loaded('sockets') && \defined('AF_INET6')) || @inet_pton('::1'))) { @@ -115,14 +130,14 @@ public static function checkIp6(string $requestIp, string $ip): bool // Check to see if we were given a IP4 $requestIp or $ip by mistake if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } if (str_contains($ip, '/')) { [$address, $netmask] = explode('/', $ip, 2); if (!filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } if ('0' === $netmask) { @@ -130,11 +145,11 @@ public static function checkIp6(string $requestIp, string $ip): bool } if ($netmask < 1 || $netmask > 128) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } } else { if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } $address = $ip; @@ -145,7 +160,7 @@ public static function checkIp6(string $requestIp, string $ip): bool $bytesTest = unpack('n*', @inet_pton($requestIp)); if (!$bytesAddr || !$bytesTest) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { @@ -153,11 +168,11 @@ public static function checkIp6(string $requestIp, string $ip): bool $left = ($left <= 16) ? $left : 16; $mask = ~(0xFFFF >> $left) & 0xFFFF; if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { - return self::$checkedIps[$cacheKey] = false; + return self::setCacheResult($cacheKey, false); } } - return self::$checkedIps[$cacheKey] = true; + return self::setCacheResult($cacheKey, true); } /** @@ -191,4 +206,36 @@ public static function anonymize(string $ip): string return $ip; } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of private IP subnets. + */ + public static function isPrivateIp(string $requestIp): bool + { + return self::checkIp($requestIp, self::PRIVATE_SUBNETS); + } + + private static function getCacheResult(string $cacheKey): ?bool + { + if (isset(self::$checkedIps[$cacheKey])) { + // Move the item last in cache (LRU) + $value = self::$checkedIps[$cacheKey]; + unset(self::$checkedIps[$cacheKey]); + self::$checkedIps[$cacheKey] = $value; + + return self::$checkedIps[$cacheKey]; + } + + return null; + } + + private static function setCacheResult(string $cacheKey, bool $result): bool + { + if (1000 < \count(self::$checkedIps)) { + // stop memory leak if there are many keys + self::$checkedIps = \array_slice(self::$checkedIps, 500, null, true); + } + + return self::$checkedIps[$cacheKey] = $result; + } } diff --git a/src/Symfony/Component/HttpFoundation/ParameterBag.php b/src/Symfony/Component/HttpFoundation/ParameterBag.php index 72c8f0949c5d4..9d7012de35d30 100644 --- a/src/Symfony/Component/HttpFoundation/ParameterBag.php +++ b/src/Symfony/Component/HttpFoundation/ParameterBag.php @@ -60,6 +60,8 @@ public function keys(): array /** * Replaces the current parameters by a new set. + * + * @return void */ public function replace(array $parameters = []) { @@ -68,6 +70,8 @@ public function replace(array $parameters = []) /** * Adds parameters. + * + * @return void */ public function add(array $parameters = []) { @@ -79,6 +83,9 @@ public function get(string $key, mixed $default = null): mixed return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; } + /** + * @return void + */ public function set(string $key, mixed $value) { $this->parameters[$key] = $value; @@ -94,6 +101,8 @@ public function has(string $key): bool /** * Removes a parameter. + * + * @return void */ public function remove(string $key) { @@ -105,7 +114,7 @@ public function remove(string $key) */ public function getAlpha(string $key, string $default = ''): string { - return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); + return preg_replace('/[^[:alpha:]]/', '', $this->getString($key, $default)); } /** @@ -113,7 +122,7 @@ public function getAlpha(string $key, string $default = ''): string */ public function getAlnum(string $key, string $default = ''): string { - return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); + return preg_replace('/[^[:alnum:]]/', '', $this->getString($key, $default)); } /** @@ -121,8 +130,20 @@ public function getAlnum(string $key, string $default = ''): string */ public function getDigits(string $key, string $default = ''): string { - // we need to remove - and + because they're allowed in the filter - return str_replace(['-', '+'], '', $this->filter($key, $default, \FILTER_SANITIZE_NUMBER_INT)); + return preg_replace('/[^[:digit:]]/', '', $this->getString($key, $default)); + } + + /** + * Returns the parameter as string. + */ + public function getString(string $key, string $default = ''): string + { + $value = $this->get($key, $default); + if (!\is_scalar($value) && !$value instanceof \Stringable) { + throw new \UnexpectedValueException(sprintf('Parameter value "%s" cannot be converted to "string".', $key)); + } + + return (string) $value; } /** @@ -130,7 +151,8 @@ public function getDigits(string $key, string $default = ''): string */ public function getInt(string $key, int $default = 0): int { - return (int) $this->get($key, $default); + // In 7.0 remove the fallback to 0, in case of failure an exception will be thrown + return $this->filter($key, $default, \FILTER_VALIDATE_INT, ['flags' => \FILTER_REQUIRE_SCALAR]) ?: 0; } /** @@ -138,13 +160,39 @@ public function getInt(string $key, int $default = 0): int */ public function getBoolean(string $key, bool $default = false): bool { - return $this->filter($key, $default, \FILTER_VALIDATE_BOOL); + return $this->filter($key, $default, \FILTER_VALIDATE_BOOL, ['flags' => \FILTER_REQUIRE_SCALAR]); + } + + /** + * Returns the parameter value converted to an enum. + * + * @template T of \BackedEnum + * + * @param class-string $class + * @param ?T $default + * + * @return ?T + */ + public function getEnum(string $key, string $class, \BackedEnum $default = null): ?\BackedEnum + { + $value = $this->get($key); + + if (null === $value) { + return $default; + } + + try { + return $class::from($value); + } catch (\ValueError|\TypeError $e) { + throw new \UnexpectedValueException(sprintf('Parameter "%s" cannot be converted to enum: %s.', $key, $e->getMessage()), $e->getCode(), $e); + } } /** * Filter key. * - * @param int $filter FILTER_* constant + * @param int $filter FILTER_* constant + * @param int|array{flags?: int, options?: array} $options Flags from FILTER_* constants * * @see https://php.net/filter-var */ @@ -162,11 +210,31 @@ public function filter(string $key, mixed $default = null, int $filter = \FILTER $options['flags'] = \FILTER_REQUIRE_ARRAY; } + if (\is_object($value) && !$value instanceof \Stringable) { + throw new \UnexpectedValueException(sprintf('Parameter value "%s" cannot be filtered.', $key)); + } + if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); } - return filter_var($value, $filter, $options); + $options['flags'] ??= 0; + $nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE; + $options['flags'] |= \FILTER_NULL_ON_FAILURE; + + $value = filter_var($value, $filter, $options); + + if (null !== $value || $nullOnFailure) { + return $value; + } + + $method = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)[1]; + $method = ($method['object'] ?? null) === $this ? $method['function'] : 'filter'; + $hint = 'filter' === $method ? 'pass' : 'use method "filter()" with'; + + trigger_deprecation('symfony/http-foundation', '6.3', 'Ignoring invalid values when using "%s::%s(\'%s\')" is deprecated and will throw an "%s" in 7.0; '.$hint.' flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', $this::class, $method, $key, \UnexpectedValueException::class); + + return false; } /** diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 80866bb3ba404..633b4a63e2e43 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -89,6 +89,8 @@ class Request /** * Request body parameters ($_POST). * + * @see getPayload() for portability between content types + * * @var InputBag */ public $request; @@ -263,6 +265,8 @@ public function __construct(array $query = [], array $request = [], array $attri * @param array $files The FILES parameters * @param array $server The SERVER parameters * @param string|resource|null $content The raw body data + * + * @return void */ public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { @@ -340,6 +344,10 @@ public static function create(string $uri, string $method = 'GET', array $parame $server['REQUEST_METHOD'] = strtoupper($method); $components = parse_url($uri); + if (false === $components) { + trigger_deprecation('symfony/http-foundation', '6.3', 'Calling "%s()" with an invalid URI is deprecated.', __METHOD__); + $components = []; + } if (isset($components['host'])) { $server['SERVER_NAME'] = $components['host']; $server['HTTP_HOST'] = $components['host']; @@ -417,6 +425,8 @@ public static function create(string $uri, string $method = 'GET', array $parame * This is mainly useful when you need to override the Request class * to keep BC with an existing system. It should not be used for any * other purpose. + * + * @return void */ public static function setFactory(?callable $callable) { @@ -521,6 +531,8 @@ public function __toString(): string * * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. * $_FILES is never overridden, see rfc1867 + * + * @return void */ public function overrideGlobals() { @@ -561,6 +573,8 @@ public function overrideGlobals() * * @param array $proxies A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR'] * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies + * + * @return void */ public static function setTrustedProxies(array $proxies, int $trustedHeaderSet) { @@ -602,12 +616,12 @@ public static function getTrustedHeaderSet(): int * You should only list the hosts you manage using regexs. * * @param array $hostPatterns A list of trusted host patterns + * + * @return void */ public static function setTrustedHosts(array $hostPatterns) { - self::$trustedHostPatterns = array_map(function ($hostPattern) { - return sprintf('{%s}i', $hostPattern); - }, $hostPatterns); + self::$trustedHostPatterns = array_map(fn ($hostPattern) => sprintf('{%s}i', $hostPattern), $hostPatterns); // we need to reset trusted hosts on trusted host patterns change self::$trustedHosts = []; } @@ -650,6 +664,8 @@ public static function normalizeQueryString(?string $qs): string * If these methods are not protected against CSRF, this presents a possible vulnerability. * * The HTTP method can only be overridden when the real HTTP method is POST. + * + * @return void */ public static function enableHttpMethodParameterOverride() { @@ -735,6 +751,9 @@ public function hasSession(bool $skipIfUninitialized = false): bool return null !== $this->session && (!$skipIfUninitialized || $this->session instanceof SessionInterface); } + /** + * @return void + */ public function setSession(SessionInterface $session) { $this->session = $session; @@ -745,7 +764,7 @@ public function setSession(SessionInterface $session) * * @param callable(): SessionInterface $factory */ - public function setSessionFactory(callable $factory) + public function setSessionFactory(callable $factory): void { $this->session = $factory; } @@ -1155,6 +1174,8 @@ public function getHost(): string /** * Sets the request method. + * + * @return void */ public function setMethod(string $method) { @@ -1276,6 +1297,8 @@ public function getFormat(?string $mimeType): ?string * Associates a format with mime types. * * @param string|string[] $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + * + * @return void */ public function setFormat(?string $format, string|array $mimeTypes) { @@ -1306,6 +1329,8 @@ public function getRequestFormat(?string $default = 'html'): ?string /** * Sets the request format. + * + * @return void */ public function setRequestFormat(?string $format) { @@ -1336,6 +1361,8 @@ public function getContentTypeFormat(): ?string /** * Sets the default locale. + * + * @return void */ public function setDefaultLocale(string $locale) { @@ -1356,6 +1383,8 @@ public function getDefaultLocale(): string /** * Sets the locale. + * + * @return void */ public function setLocale(string $locale) { @@ -1477,9 +1506,19 @@ public function getContent(bool $asResource = false) return $this->content; } + /** + * Gets the decoded form or json request body. + */ + public function getPayload(): InputBag + { + return $this->request->count() ? clone $this->request : new InputBag($this->toArray()); + } + /** * Gets the request body decoded as array, typically from a JSON payload. * + * @see getPayload() for portability between content types + * * @throws JsonException When the body cannot be decoded to an array */ public function toArray(): array @@ -1693,6 +1732,9 @@ public function preferSafeContent(): bool * Copyright (c) 2005-2010 Zend Technologies USA Inc. (https://www.zend.com/) */ + /** + * @return string + */ protected function prepareRequestUri() { $requestUri = ''; @@ -1861,6 +1903,8 @@ protected function preparePathInfo(): string /** * Initializes HTTP request formats. + * + * @return void */ protected static function initializeFormats() { diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher.php index c2addd36e8ad1..8c5f1d8134635 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher.php @@ -69,6 +69,8 @@ public function __construct(string $path = null, string $host = null, string|arr * Adds a check for the HTTP scheme. * * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes + * + * @return void */ public function matchScheme(string|array|null $scheme) { @@ -77,6 +79,8 @@ public function matchScheme(string|array|null $scheme) /** * Adds a check for the URL host name. + * + * @return void */ public function matchHost(?string $regexp) { @@ -87,6 +91,8 @@ public function matchHost(?string $regexp) * Adds a check for the the URL port. * * @param int|null $port The port number to connect to + * + * @return void */ public function matchPort(?int $port) { @@ -95,6 +101,8 @@ public function matchPort(?int $port) /** * Adds a check for the URL path info. + * + * @return void */ public function matchPath(?string $regexp) { @@ -105,6 +113,8 @@ public function matchPath(?string $regexp) * Adds a check for the client IP. * * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + * + * @return void */ public function matchIp(string $ip) { @@ -115,20 +125,22 @@ public function matchIp(string $ip) * Adds a check for the client IP. * * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + * + * @return void */ public function matchIps(string|array|null $ips) { $ips = null !== $ips ? (array) $ips : []; - $this->ips = array_reduce($ips, static function (array $ips, string $ip) { - return array_merge($ips, preg_split('/\s*,\s*/', $ip)); - }, []); + $this->ips = array_reduce($ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []); } /** * Adds a check for the HTTP method. * * @param string|string[]|null $method An HTTP method or an array of HTTP methods + * + * @return void */ public function matchMethod(string|array|null $method) { @@ -137,6 +149,8 @@ public function matchMethod(string|array|null $method) /** * Adds a check for request attribute. + * + * @return void */ public function matchAttribute(string $key, string $regexp) { diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/IpsRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/IpsRequestMatcher.php index 2ddff038df769..333612e2f29b2 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher/IpsRequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/IpsRequestMatcher.php @@ -30,9 +30,7 @@ class IpsRequestMatcher implements RequestMatcherInterface */ public function __construct(array|string $ips) { - $this->ips = array_reduce((array) $ips, static function (array $ips, string $ip) { - return array_merge($ips, preg_split('/\s*,\s*/', $ip)); - }, []); + $this->ips = array_reduce((array) $ips, static fn (array $ips, string $ip) => array_merge($ips, preg_split('/\s*,\s*/', $ip)), []); } public function matches(Request $request): bool diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/IsJsonRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/IsJsonRequestMatcher.php index 5da46840f4fd1..875f992be156a 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher/IsJsonRequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/IsJsonRequestMatcher.php @@ -23,12 +23,6 @@ class IsJsonRequestMatcher implements RequestMatcherInterface { public function matches(Request $request): bool { - try { - json_decode($request->getContent(), true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); - } catch (\JsonException) { - return false; - } - - return true; + return json_validate($request->getContent()); } } diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/MethodRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/MethodRequestMatcher.php index c7a915980c239..b37f6e3c87f96 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher/MethodRequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/MethodRequestMatcher.php @@ -32,9 +32,7 @@ class MethodRequestMatcher implements RequestMatcherInterface */ public function __construct(array|string $methods) { - $this->methods = array_reduce(array_map('strtoupper', (array) $methods), static function (array $methods, string $method) { - return array_merge($methods, preg_split('/\s*,\s*/', $method)); - }, []); + $this->methods = array_reduce(array_map('strtoupper', (array) $methods), static fn (array $methods, string $method) => array_merge($methods, preg_split('/\s*,\s*/', $method)), []); } public function matches(Request $request): bool diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/SchemeRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/SchemeRequestMatcher.php index 4f5eabc2c5ba1..9c9cd58b983cc 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher/SchemeRequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/SchemeRequestMatcher.php @@ -32,9 +32,7 @@ class SchemeRequestMatcher implements RequestMatcherInterface */ public function __construct(array|string $schemes) { - $this->schemes = array_reduce(array_map('strtolower', (array) $schemes), static function (array $schemes, string $scheme) { - return array_merge($schemes, preg_split('/\s*,\s*/', $scheme)); - }, []); + $this->schemes = array_reduce(array_map('strtolower', (array) $schemes), static fn (array $schemes, string $scheme) => array_merge($schemes, preg_split('/\s*,\s*/', $scheme)), []); } public function matches(Request $request): bool diff --git a/src/Symfony/Component/HttpFoundation/RequestStack.php b/src/Symfony/Component/HttpFoundation/RequestStack.php index 6b13fa1e6ec23..5aa8ba793414c 100644 --- a/src/Symfony/Component/HttpFoundation/RequestStack.php +++ b/src/Symfony/Component/HttpFoundation/RequestStack.php @@ -31,6 +31,8 @@ class RequestStack * * This method should generally not be called directly as the stack * management should be taken care of by the application itself. + * + * @return void */ public function push(Request $request) { diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index c141cbc0bb280..888c6ad858aaa 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -211,6 +211,11 @@ class Response 511 => 'Network Authentication Required', // RFC6585 ]; + /** + * Tracks headers already sent in informational responses. + */ + private array $sentHeaders; + /** * @param int $status The HTTP status code (200 "OK" by default) * @@ -326,21 +331,54 @@ public function prepare(Request $request): static /** * Sends HTTP headers. * + * @param null|positive-int $statusCode The status code to use, override the statusCode property if set and not null + * * @return $this */ - public function sendHeaders(): static + public function sendHeaders(/* int $statusCode = null */): static { // headers have already been sent by the developer if (headers_sent()) { return $this; } + $statusCode = \func_num_args() > 0 ? func_get_arg(0) : null; + $informationalResponse = $statusCode >= 100 && $statusCode < 200; + if ($informationalResponse && !\function_exists('headers_send')) { + // skip informational responses if not supported by the SAPI + return $this; + } + // headers foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { - $replace = 0 === strcasecmp($name, 'Content-Type'); - foreach ($values as $value) { + $newValues = $values; + $replace = false; + + // As recommended by RFC 8297, PHP automatically copies headers from previous 103 responses, we need to deal with that 10000 if headers changed + if (103 === $statusCode) { + $previousValues = $this->sentHeaders[$name] ?? null; + if ($previousValues === $values) { + // Header already sent in a previous response, it will be automatically copied in this response by PHP + continue; + } + + $replace = 0 === strcasecmp($name, 'Content-Type'); + + if (null !== $previousValues && array_diff($previousValues, $values)) { + header_remove($name); + $previousValues = null; + } + + $newValues = null === $previousValues ? $values : array_diff($values, $previousValues); + } + + foreach ($newValues as $value) { header($name.': '.$value, $replace, $this->statusCode); } + + if ($informationalResponse) { + $this->sentHeaders[$name] = $values; + } } // cookies @@ -348,8 +386,16 @@ public function sendHeaders(): static header('Set-Cookie: '.$cookie, false, $this->statusCode); } + if ($informationalResponse) { + headers_send($statusCode); + + return $this; + } + + $statusCode ??= $this->statusCode; + // status - header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + header(sprintf('HTTP/%s %s %s', $this->version, $statusCode, $this->statusText), true, $statusCode); return $this; } diff --git a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php index 9e8c5793a7668..10450ca5e21d8 100644 --- a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php @@ -55,6 +55,9 @@ public function allPreserveCase(): array return $headers; } + /** + * @return array + */ public function allPreserveCaseWithoutCookies() { $headers = $this->allPreserveCase(); @@ -65,6 +68,9 @@ public function allPreserveCaseWithoutCookies() return $headers; } + /** + * @return void + */ public function replace(array $headers = []) { $this->headerNames = []; @@ -97,6 +103,9 @@ public function all(string $key = null): array return $headers; } + /** + * @return void + */ public function set(string $key, string|array|null $values, bool $replace = true) { $uniqueKey = strtr($key, self::UPPER, self::LOWER); @@ -125,6 +134,9 @@ public function set(string $key, string|array|null $values, bool $replace = true } } + /** + * @return void + */ public function remove(string $key) { $uniqueKey = strtr($key, self::UPPER, self::LOWER); @@ -157,6 +169,9 @@ public function getCacheControlDirective(string $key): bool|string|null return $this->computedCacheControl[$key] ?? null; } + /** + * @return void + */ public function setCookie(Cookie $cookie) { $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; @@ -165,6 +180,8 @@ public function setCookie(Cookie $cookie) /** * Removes a cookie from the array, but does not unset it in the browser. + * + * @return void */ public function removeCookie(string $name, ?string $path = '/', string $domain = null) { @@ -216,6 +233,8 @@ public function getCookies(string $format = self::COOKIES_FLAT): array /** * Clears a cookie in the browser. + * + * @return void */ public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true, string $sameSite = null) { @@ -224,6 +243,8 @@ public function clearCookie(string $name, ?string $path = '/', string $domain = /** * @see HeaderUtils::makeDisposition() + * + * @return string */ public function makeDisposition(string $disposition, string $filename, string $filenameFallback = '') { diff --git a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php index 665961757982e..ad5a6590a57ac 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php +++ b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php @@ -36,11 +36,17 @@ public function getName(): string return $this->name; } + /** + * @return void + */ public function setName(string $name) { $this->name = $name; } + /** + * @return void + */ public function initialize(array &$attributes) { $this->attributes = &$attributes; @@ -61,6 +67,9 @@ public function get(string $name, mixed $default = null): mixed return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; } + /** + * @return void + */ public function set(string $name, mixed $value) { $this->attributes[$name] = $value; @@ -71,6 +80,9 @@ public function all(): array return $this->attributes; } + /** + * @return void + */ public function replace(array $attributes) { $this->attributes = []; diff --git a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php index 31a946444b93f..e8cd0b5a4d6e3 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php +++ b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php @@ -32,6 +32,8 @@ public function get(string $name, mixed $default = null): mixed; /** * Sets an attribute. + * + * @return void */ public function set(string $name, mixed $value); @@ -42,6 +44,9 @@ public function set(string $name, mixed $value); */ public function all(): array; + /** + * @return void + */ public function replace(array $attributes); /** diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php b/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php index 00b1ac94866ce..80bbeda0f8828 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php +++ b/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php @@ -35,11 +35,17 @@ public function getName(): string return $this->name; } + /** + * @return void + */ public function setName(string $name) { $this->name = $name; } + /** + * @return void + */ public function initialize(array &$flashes) { $this->flashes = &$flashes; @@ -51,6 +57,9 @@ public function initialize(array &$flashes) $this->flashes['new'] = []; } + /** + * @return void + */ public function add(string $type, mixed $message) { $this->flashes['new'][$type][] = $message; @@ -90,11 +99,17 @@ public function all(): array return $return; } + /** + * @return void + */ public function setAll(array $messages) { $this->flashes['new'] = $messages; } + /** + * @return void + */ public function set(string $type, string|array $messages) { $this->flashes['new'][$type] = (array) $messages; diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php index a30d9528d1ff8..659d59d18699f 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php +++ b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php @@ -35,16 +35,25 @@ public function getName(): string return $this->name; } + /** + * @return void + */ public function setName(string $name) { $this->name = $name; } + /** + * @return void + */ public function initialize(array &$flashes) { $this->flashes = &$flashes; } + /** + * @return void + */ public function add(string $type, mixed $message) { $this->flashes[$type][] = $message; @@ -81,11 +90,17 @@ public function all(): array return $return; } + /** + * @return void + */ public function set(string $type, string|array $messages) { $this->flashes[$type] = (array) $messages; } + /** + * @return void + */ public function setAll(array $messages) { $this->flashes = $messages; diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php index cd10a23f3c08e..bbcf7f8b7d877 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php +++ b/src/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php @@ -22,11 +22,15 @@ interface FlashBagInterface extends SessionBagInterface { /** * Adds a flash message for the given type. + * + * @return void */ public function add(string $type, mixed $message); /** * Registers one or more messages for a given type. + * + * @return void */ public function set(string $type, string|array $messages); @@ -57,6 +61,8 @@ public function all(): array; /** * Sets all flash messages. + * + * @return void */ public function setAll(array $messages); diff --git a/src/Symfony/Component/HttpFoundation/Session/Session.php b/src/Symfony/Component/HttpFoundation/Session/Session.php index a55dde4821a96..b45be2f8c36a7 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Session.php +++ b/src/Symfony/Component/HttpFoundation/Session/Session.php @@ -69,6 +69,9 @@ public function get(string $name, mixed $default = null): mixed return $this->getAttributeBag()->get($name, $default); } + /** + * @return void + */ public function set(string $name, mixed $value) { $this->getAttributeBag()->set($name, $value); @@ -79,6 +82,9 @@ public function all(): array return $this->getAttributeBag()->all(); } + /** + * @return void + */ public function replace(array $attributes) { $this->getAttributeBag()->replace($attributes); @@ -89,6 +95,9 @@ public function remove(string $name): mixed return $this->getAttributeBag()->remove($name); } + /** + * @return void + */ public function clear() { $this->getAttributeBag()->clear(); @@ -154,6 +163,9 @@ public function migrate(bool $destroy = false, int $lifetime = null): bool return $this->storage->regenerate($destroy, $lifetime); } + /** + * @return void + */ public function save() { $this->storage->save(); @@ -164,6 +176,9 @@ public function getId(): string return $this->storage->getId(); } + /** + * @return void + */ public function setId(string $id) { if ($this->storage->getId() !== $id) { @@ -176,6 +191,9 @@ public function getName(): string return $this->storage->getName(); } + /** + * @return void + */ public function setName(string $name) { $this->storage->setName($name); @@ -191,6 +209,9 @@ public function getMetadataBag(): MetadataBag return $this->storage->getMetadataBag(); } + /** + * @return void + */ public function registerBag(SessionBagInterface $bag) { $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex, $this->usageReporter)); diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php b/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php index 821645d9b87c6..e1c2505549578 100644 --- a/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php +++ b/src/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php @@ -25,6 +25,8 @@ public function getName(): string; /** * Initializes the Bag. + * + * @return void */ public function initialize(array &$array); diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php b/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php index da2b3a37d6803..534883d2d227f 100644 --- a/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php +++ b/src/Symfony/Component/HttpFoundation/Session/SessionInterface.php @@ -34,6 +34,8 @@ public function getId(): string; /** * Sets the session ID. + * + * @return void */ public function setId(string $id); @@ -44,6 +46,8 @@ public function getName(): string; /** * Sets the session name. + * + * @return void */ public function setName(string $name); @@ -78,6 +82,8 @@ public function migrate(bool $destroy = false, int $lifetime = null): bool; * This method is generally not required for real sessions as * the session will be automatically saved at the end of * code execution. + * + * @return void */ public function save(); @@ -93,6 +99,8 @@ public function get(string $name, mixed $default = null): mixed; /** * Sets an attribute. + * + * @return void */ public function set(string $name, mixed $value); @@ -103,6 +111,8 @@ public function all(): array; /** * Sets attributes. + * + * @return void */ public function replace(array $attributes); @@ -115,6 +125,8 @@ public function remove(string $name): mixed; /** * Clears all attributes. + * + * @return void */ public function clear(); @@ -125,6 +137,8 @@ public function isStarted(): bool; /** * Registers a SessionBagInterface with the session. + * + * @return void */ public function registerBag(SessionBagInterface $bag); diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionUtils.php b/src/Symfony/Component/HttpFoundation/Session/SessionUtils.php index b5bce4a884eff..504c5848e9dd9 100644 --- a/src/Symfony/Component/HttpFoundation/Session/SessionUtils.php +++ b/src/Symfony/Component/HttpFoundation/Session/SessionUtils.php @@ -25,7 +25,7 @@ final class SessionUtils * Finds the session header amongst the headers that are to be sent, removes it, and returns * it so the caller can process it further. */ - public static function popSessionCookie(string $sessionName, string $sessionId): ?string + public static function popSessionCookie(string $sessionName, #[\SensitiveParameter] string $sessionId): ?string { $sessionCookie = null; $sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName)); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php index 88e513c5ddd3f..288c24232c89d 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -38,13 +38,13 @@ public function open(string $savePath, string $sessionName): bool return true; } - abstract protected function doRead(string $sessionId): string; + abstract protected function doRead(#[\SensitiveParameter] string $sessionId): string; - abstract protected function doWrite(string $sessionId, string $data): bool; + abstract protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool; - abstract protected function doDestroy(string $sessionId): bool; + abstract protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool; - public function validateId(string $sessionId): bool + public function validateId(#[\SensitiveParameter] string $sessionId): bool { $this->prefetchData = $this->read($sessionId); $this->prefetchId = $sessionId; @@ -52,7 +52,7 @@ public function validateId(string $sessionId): bool return '' !== $this->prefetchData; } - public function read(string $sessionId): string + public function read(#[\SensitiveParameter] string $sessionId): string { if (isset($this->prefetchId)) { $prefetchId = $this->prefetchId; @@ -72,7 +72,7 @@ public function read(string $sessionId): string return $data; } - public function write(string $sessionId, string $data): bool + public function write(#[\SensitiveParameter] string $sessionId, string $data): bool { // see https://github.com/igbinary/igbinary/issues/146 $this->igbinaryEmptyData ??= \function_exists('igbinary_serialize') ? igbinary_serialize([]) : ''; @@ -84,7 +84,7 @@ public function write(string $sessionId, string $data): bool return $this->doWrite($sessionId, $data); } - public function destroy(string $sessionId): bool + public function destroy(#[\SensitiveParameter] string $sessionId): bool { if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOL)) { if (!isset($this->sessionName)) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MarshallingSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MarshallingSessionHandler.php index 9962fef3d6c4c..1567f54332c51 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MarshallingSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MarshallingSessionHandler.php @@ -37,7 +37,7 @@ public function close(): bool return $this->handler->close(); } - public function destroy(string $sessionId): bool + public function destroy(#[\SensitiveParameter] string $sessionId): bool { return $this->handler->destroy($sessionId); } @@ -47,12 +47,12 @@ public function gc(int $maxlifetime): int|false return $this->handler->gc($maxlifetime); } - public function read(string $sessionId): string + public function read(#[\SensitiveParameter] string $sessionId): string { return $this->marshaller->unmarshall($this->handler->read($sessionId)); } - public function write(string $sessionId, string $data): bool + public function write(#[\SensitiveParameter] string $sessionId, string $data): bool { $failed = []; $marshalledData = $this->marshaller->marshall(['data' => $data], $failed); @@ -64,12 +64,12 @@ public function write(string $sessionId, string $data): bool return $this->handler->write($sessionId, $marshalledData['data']); } - public function validateId(string $sessionId): bool + public function validateId(#[\SensitiveParameter] string $sessionId): bool { return $this->handler->validateId($sessionId); } - public function updateTimestamp(string $sessionId, string $data): bool + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->handler->updateTimestamp($sessionId, $data); } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php index 2bc29b459cacd..91a023ddbc119 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -59,19 +59,19 @@ public function close(): bool return $this->memcached->quit(); } - protected function doRead(string $sessionId): string + protected function doRead(#[\SensitiveParameter] string $sessionId): string { return $this->memcached->get($this->prefix.$sessionId) ?: ''; } - public function updateTimestamp(string $sessionId, string $data): bool + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { $this->memcached->touch($this->prefix.$sessionId, $this->getCompatibleTtl()); return true; } - protected function doWrite(string $sessionId, string $data): bool + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->memcached->set($this->prefix.$sessionId, $data, $this->getCompatibleTtl()); } @@ -89,7 +89,7 @@ private function getCompatibleTtl(): int return $ttl; } - protected function doDestroy(string $sessionId): bool + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { $result = $this->memcached->delete($this->prefix.$sessionId); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php index 1d425523681a1..8ed6a7b3fd08b 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php @@ -22,15 +22,8 @@ */ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface { - /** - * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface - */ - private \SessionHandlerInterface $currentHandler; - - /** - * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface - */ - private \SessionHandlerInterface $writeOnlyHandler; + private \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface $currentHandler; + private \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface $writeOnlyHandler; public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler) { @@ -53,7 +46,7 @@ public function close(): bool return $result; } - public function destroy(string $sessionId): bool + public function destroy(#[\SensitiveParameter] string $sessionId): bool { $result = $this->currentHandler->destroy($sessionId); $this->writeOnlyHandler->destroy($sessionId); @@ -77,13 +70,13 @@ public function open(string $savePath, string $sessionName): bool return $result; } - public function read(string $sessionId): string + public function read(#[\SensitiveParameter] string $sessionId): string { // No reading from new handler until switch-over return $this->currentHandler->read($sessionId); } - public function write(string $sessionId, string $sessionData): bool + public function write(#[\SensitiveParameter] string $sessionId, string $sessionData): bool { $result = $this->currentHandler->write($sessionId, $sessionData); $this->writeOnlyHandler->write($sessionId, $sessionData); @@ -91,13 +84,13 @@ public function write(string $sessionId, string $sessionData): bool return $result; } - public function validateId(string $sessionId): bool + public function validateId(#[\SensitiveParameter] string $sessionId): bool { // No reading from new handler until switch-over return $this->currentHandler->validateId($sessionId); } - public function updateTimestamp(string $sessionId, string $sessionData): bool + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $sessionData): bool { $result = $this->currentHandler->updateTimestamp($sessionId, $sessionData); $this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index 63c609ae21657..5ea5b4ae7d98d 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -84,7 +84,7 @@ public function close(): bool return true; } - protected function doDestroy(string $sessionId): bool + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { $this->getCollection()->deleteOne([ $this->options['id_field'] => $sessionId, @@ -100,7 +100,7 @@ public function gc(int $maxlifetime): int|false ])->getDeletedCount(); } - protected function doWrite(string $sessionId, string $data): bool + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); $expiry = new UTCDateTime((time() + (int) $ttl) * 1000); @@ -120,7 +120,7 @@ protected function doWrite(string $sessionId, string $data): bool return true; } - public function updateTimestamp(string $sessionId, string $data): bool + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); $expiry = new UTCDateTime((time() + (int) $ttl) * 1000); @@ -136,7 +136,7 @@ public function updateTimestamp(string $sessionId, string $data): bool return true; } - protected function doRead(string $sessionId): string + protected function doRead(#[\SensitiveParameter] string $sessionId): string { $dbData = $this->getCollection()->findOne([ $this->options['id_field'] => $sessionId, diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php index 790ac2fedd8f5..a77185e2ebf77 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php @@ -23,27 +23,27 @@ public function close(): bool return true; } - public function validateId(string $sessionId): bool + public function validateId(#[\SensitiveParameter] string $sessionId): bool { return true; } - protected function doRead(string $sessionId): string + protected function doRead(#[\SensitiveParameter] string $sessionId): string { return ''; } - public function updateTimestamp(string $sessionId, string $data): bool + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { return true; } - protected function doWrite(string $sessionId, string $data): bool + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { return true; } - protected function doDestroy(string $sessionId): bool + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { return true; } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 06429b4ba0e33..65452a5207374 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -11,6 +11,9 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Types\Types; + /** * Session handler using a PDO connection to read and write data. * @@ -148,7 +151,7 @@ class PdoSessionHandler extends AbstractSessionHandler * * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION */ - public function __construct(\PDO|string $pdoOrDsn = null, array $options = []) + public function __construct(#[\SensitiveParameter] \PDO|string $pdoOrDsn = null, #[\SensitiveParameter] array $options = []) { if ($pdoOrDsn instanceof \PDO) { if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { @@ -175,6 +178,56 @@ public function __construct(\PDO|string $pdoOrDsn = null, array $options = []) $this->ttl = $options['ttl'] ?? null; } + /** + * Adds the Table to the Schema if it doesn't exist. + */ + public function configureSchema(Schema $schema, \Closure $isSameDatabase = null): void + { + if ($schema->hasTable($this->table) || ($isSameDatabase && !$isSameDatabase($this->getConnection()->exec(...)))) { + return; + } + + $table = $schema->createTable($this->table); + switch ($this->driver) { + case 'mysql': + $table->addColumn($this->idCol, Types::BINARY)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addOption('collate', 'utf8mb4_bin'); + $table->addOption('engine', 'InnoDB'); + break; + case 'sqlite': + $table->addColumn($this->idCol, Types::TEXT)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'pgsql': + $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BINARY)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'oci': + $table->addColumn($this->idCol, Types::STRING)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setNotnull(true); + break; + case 'sqlsrv': + $table->addColumn($this->idCol, Types::TEXT)->setLength(128)->setNotnull(true); + $table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true); + $table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + $table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true); + break; + default: + throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); + } + $table->setPrimaryKey([$this->idCol]); + $table->addIndex([$this->lifetimeCol], $this->lifetimeCol.'_idx'); + } + /** * Creates the table to store sessions which can be called once for setup. * @@ -183,6 +236,8 @@ public function __construct(\PDO|string $pdoOrDsn = null, array $options = []) * saved in a BLOB. One could also use a shorter inlined varbinary column * if one was sure the data fits into it. * + * @return void + * * @throws \PDOException When the table already exists * @throws \DomainException When an unsupported PDO driver is used */ @@ -207,7 +262,7 @@ public function createTable() try { $this->pdo->exec($sql); - $this->pdo->exec("CREATE INDEX expiry ON $this->table ($this->lifetimeCol)"); + $this->pdo->exec("CREATE INDEX {$this->lifetimeCol}_idx ON $this->table ($this->lifetimeCol)"); } catch (\PDOException $e) { $this->rollback(); @@ -236,7 +291,7 @@ public function open(string $savePath, string $sessionName): bool return parent::open($savePath, $sessionName); } - public function read(string $sessionId): string + public function read(#[\SensitiveParameter] string $sessionId): string { try { return parent::read($sessionId); @@ -256,7 +311,7 @@ public function gc(int $maxlifetime): int|false return 0; } - protected function doDestroy(string $sessionId): bool + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { // delete the record associated with this id $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; @@ -274,7 +329,7 @@ protected function doDestroy(string $sessionId): bool return true; } - protected function doWrite(string $sessionId, string $data): bool + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { $maxlifetime = (int) (($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime')); @@ -317,7 +372,7 @@ protected function doWrite(string $sessionId, string $data): bool return true; } - public function updateTimestamp(string $sessionId, string $data): bool + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { $expiry = time() + (int) (($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime')); @@ -366,7 +421,7 @@ public function close(): bool /** * Lazy-connects to the database. */ - private function connect(string $dsn): void + private function connect(#[\SensitiveParameter] string $dsn): void { $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); @@ -378,7 +433,7 @@ private function connect(string $dsn): void * * @todo implement missing support for oci DSN (which look totally different from other PDO ones) */ - private function buildDsnFromUrl(string $dsnOrUrl): string + private function buildDsnFromUrl(#[\SensitiveParameter] string $dsnOrUrl): string { // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); @@ -561,7 +616,7 @@ private function rollback(): void * We need to make sure we do not return session data that is already considered garbage according * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. */ - protected function doRead(string $sessionId): string + protected function doRead(#[\SensitiveParameter] string $sessionId): string { if (self::LOCK_ADVISORY === $this->lockMode) { $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); @@ -632,7 +687,7 @@ protected function doRead(string $sessionId): string * - for oci using DBMS_LOCK.REQUEST * - for sqlsrv using sp_getapplock with LockOwner = Session */ - private function doAdvisoryLock(string $sessionId): \PDOStatement + private function doAdvisoryLock(#[\SensitiveParameter] string $sessionId): \PDOStatement { switch ($this->driver) { case 'mysql': @@ -731,7 +786,7 @@ private function getSelectSql(): string /** * Returns an insert statement supported by the database for writing session data. */ - private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + private function getInsertStatement(#[\SensitiveParameter] string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement { switch ($this->driver) { case 'oci': @@ -758,7 +813,7 @@ private function getInsertStatement(string $sessionId, string $sessionData, int /** * Returns an update statement supported by the database for writing session data. */ - private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + private function getUpdateStatement(#[\SensitiveParameter] string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement { switch ($this->driver) { case 'oci': @@ -785,7 +840,7 @@ private function getUpdateStatement(string $sessionId, string $sessionData, int /** * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. */ - private function getMergeStatement(string $sessionId, string $data, int $maxlifetime): ?\PDOStatement + private function getMergeStatement(#[\SensitiveParameter] string $sessionId, string $data, int $maxlifetime): ?\PDOStatement { switch (true) { case 'mysql' === $this->driver: diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php index 38f488644deba..b696eee4b7d1f 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; use Predis\Response\ErrorInterface; +use Relay\Relay; /** * Redis based session storage handler based on the Redis class @@ -39,7 +40,7 @@ class RedisSessionHandler extends AbstractSessionHandler * @throws \InvalidArgumentException When unsupported client or options are passed */ public function __construct( - private \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, array $options = [], ) { if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) { @@ -50,12 +51,12 @@ public function __construct( $this->ttl = $options['ttl'] ?? null; } - protected function doRead(string $sessionId): string + protected function doRead(#[\SensitiveParameter] string $sessionId): string { return $this->redis->get($this->prefix.$sessionId) ?: ''; } - protected function doWrite(string $sessionId, string $data): bool + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); $result = $this->redis->setEx($this->prefix.$sessionId, (int) $ttl, $data); @@ -63,7 +64,7 @@ protected function doWrite(string $sessionId, string $data): bool return $result && !$result instanceof ErrorInterface; } - protected function doDestroy(string $sessionId): bool + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { static $unlink = true; @@ -93,7 +94,7 @@ public function gc(int $maxlifetime): int|false return 0; } - public function updateTimestamp(string $sessionId, string $data): bool + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php index e390c8feea5cf..dbbe7dc880e23 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; use Doctrine\DBAL\DriverManager; +use Relay\Relay; use Symfony\Component\Cache\Adapter\AbstractAdapter; /** @@ -32,6 +33,7 @@ public static function createHandler(object|string $connection, array $options = switch (true) { case $connection instanceof \Redis: + case $connection instanceof Relay: case $connection instanceof \RedisArray: case $connection instanceof \RedisCluster: case $connection instanceof \Predis\ClientInterface: @@ -54,7 +56,7 @@ public static function createHandler(object|string $connection, array $options = case str_starts_with($connection, 'rediss:'): case str_starts_with($connection, 'memcached:'): if (!class_exists(AbstractAdapter::class)) { - throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection)); + throw new \InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".'); } $handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class; $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); @@ -63,7 +65,7 @@ public static function createHandler(object|string $connection, array $options = case str_starts_with($connection, 'pdo_oci://'): if (!class_exists(DriverManager::class)) { - throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection)); + throw new \InvalidArgumentException('Unsupported PDO OCI DSN. Try running "composer require doctrine/dbal".'); } $connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection(); // no break; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php index 1a163648155d1..1f8668744762d 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php @@ -47,22 +47,22 @@ public function open(string $savePath, string $sessionName): bool return $this->handler->open($savePath, $sessionName); } - protected function doRead(string $sessionId): string + protected function doRead(#[\SensitiveParameter] string $sessionId): string { return $this->handler->read($sessionId); } - public function updateTimestamp(string $sessionId, string $data): bool + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->write($sessionId, $data); } - protected function doWrite(string $sessionId, string $data): bool + protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->handler->write($sessionId, $data); } - public function destroy(string $sessionId): bool + public function destroy(#[\SensitiveParameter] string $sessionId): bool { $this->doDestroy = true; $destroyed = parent::destroy($sessionId); @@ -70,7 +70,7 @@ public function destroy(string $sessionId): bool return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed; } - protected function doDestroy(string $sessionId): bool + protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool { $this->doDestroy = false; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php index 2c77f9db6c0fb..ebe4b748ad756 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php @@ -51,6 +51,9 @@ public function __construct(string $storageKey = '_sf2_meta', int $updateThresho $this->updateThreshold = $updateThreshold; } + /** + * @return void + */ public function initialize(array &$array) { $this->meta = &$array; @@ -82,6 +85,8 @@ public function getLifetime(): int * will leave the system settings unchanged, 0 sets the cookie * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. + * + * @return void */ public function stampNew(int $lifetime = null) { @@ -126,6 +131,8 @@ public function getName(): string /** * Sets name. + * + * @return void */ public function setName(string $name) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php index 67fa0f95e031a..d30b56d691ec0 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php @@ -68,6 +68,9 @@ public function __construct(string $name = 'MOCKSESSID', MetadataBag $metaBag = $this->setMetadataBag($metaBag); } + /** + * @return void + */ public function setSessionData(array $array) { $this->data = $array; @@ -105,6 +108,9 @@ public function getId(): string return $this->id; } + /** + * @return void + */ public function setId(string $id) { if ($this->started) { @@ -119,11 +125,17 @@ public function getName(): string return $this->name; } + /** + * @return void + */ public function setName(string $name) { $this->name = $name; } + /** + * @return void + */ public function save() { if (!$this->started || $this->closed) { @@ -134,6 +146,9 @@ public function save() $this->started = false; } + /** + * @return void + */ public function clear() { // clear out the bags @@ -148,6 +163,9 @@ public function clear() $this->loadSession(); } + /** + * @return void + */ public function registerBag(SessionBagInterface $bag) { $this->bags[$bag->getName()] = $bag; @@ -171,6 +189,9 @@ public function isStarted(): bool return $this->started; } + /** + * @return void + */ public function setMetadataBag(MetadataBag $bag = null) { if (1 > \func_num_args()) { @@ -198,6 +219,9 @@ protected function generateId(): string return hash('sha256', uniqid('ss_mock_', true)); } + /** + * @return void + */ protected function loadSession() { $bags = array_merge($this->bags, [$this->metadataBag]); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php index 28771ad54ebb5..95f69f2e1385b 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php @@ -73,6 +73,9 @@ public function regenerate(bool $destroy = false, int $lifetime = null): bool return parent::regenerate($destroy, $lifetime); } + /** + * @return void + */ public function save() { if (!$this->started) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index 8b89478820fc2..7c6b6f9296c6f 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -183,6 +183,9 @@ public function getId(): string return $this->saveHandler->getId(); } + /** + * @return void + */ public function setId(string $id) { $this->saveHandler->setId($id); @@ -193,6 +196,9 @@ public function getName(): string return $this->saveHandler->getName(); } + /** + * @return void + */ public function setName(string $name) { $this->saveHandler->setName($name); @@ -222,6 +228,9 @@ public function regenerate(bool $destroy = false, int $lifetime = null): bool return session_regenerate_id($destroy); } + /** + * @return void + */ public function save() { // Store a copy so we can restore the bags in case the session was not left empty @@ -261,6 +270,9 @@ public function save() $this->started = false; } + /** + * @return void + */ public function clear() { // clear out the bags @@ -275,6 +287,9 @@ public function clear() $this->loadSession(); } + /** + * @return void + */ public function registerBag(SessionBagInterface $bag) { if ($this->started) { @@ -299,6 +314,9 @@ public function getBag(string $name): SessionBagInterface return $this->bags[$name]; } + /** + * @return void + */ public function setMetadataBag(MetadataBag $metaBag = null) { if (1 > \func_num_args()) { @@ -329,6 +347,8 @@ public function isStarted(): bool * @param array $options Session ini directives [key => value] * * @see https://php.net/session.configuration + * + * @return void */ public function setOptions(array $options) { @@ -372,6 +392,8 @@ public function setOptions(array $options) * @see https://php.net/sessionhandlerinterface * @see https://php.net/sessionhandler * + * @return void + * * @throws \InvalidArgumentException */ public function setSaveHandler(AbstractProxy|\SessionHandlerInterface $saveHandler = null) @@ -404,6 +426,8 @@ public function setSaveHandler(AbstractProxy|\SessionHandlerInterface $saveHandl * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). * PHP takes the return value from the read() handler, unserializes it * and populates $_SESSION with the result automatically. + * + * @return void */ protected function loadSession(array &$session = null) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php index eed748f811eb7..28cb3c3d05983 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php @@ -41,6 +41,9 @@ public function start(): bool return true; } + /** + * @return void + */ public function clear() { // clear out the bags and nothing else that may be set diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php index 1845ee2c9e9fe..2fcd06b10b17c 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php @@ -71,6 +71,8 @@ public function getId(): string /** * Sets the session ID. * + * @return void + * * @throws \LogicException */ public function setId(string $id) @@ -93,6 +95,8 @@ public function getName(): string /** * Sets the session name. * + * @return void + * * @throws \LogicException */ public function setName(string $name) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php index c292e58f05851..7bf3f9ff1e1dc 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -44,17 +44,17 @@ public function close(): bool return $this->handler->close(); } - public function read(string $sessionId): string|false + public function read(#[\SensitiveParameter] string $sessionId): string|false { return $this->handler->read($sessionId); } - public function write(string $sessionId, string $data): bool + public function write(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->handler->write($sessionId, $data); } - public function destroy(string $sessionId): bool + public function destroy(#[\SensitiveParameter] string $sessionId): bool { return $this->handler->destroy($sessionId); } @@ -64,12 +64,12 @@ public function gc(int $maxlifetime): int|false return $this->handler->gc($maxlifetime); } - public function validateId(string $sessionId): bool + public function validateId(#[\SensitiveParameter] string $sessionId): bool { return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId); } - public function updateTimestamp(string $sessionId, string $data): bool + public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool { return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data); } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php index 8bd62a43ae894..ed2189e4e777c 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php @@ -40,6 +40,8 @@ public function getId(): string; /** * Sets the session ID. + * + * @return void */ public function setId(string $id); @@ -50,6 +52,8 @@ public function getName(): string; /** * Sets the session name. + * + * @return void */ public function setName(string $name); @@ -90,6 +94,8 @@ public function regenerate(bool $destroy = false, int $lifetime = null): bool; * a real PHP session would interfere with testing, in which case * it should actually persist the session data if required. * + * @return void + * * @throws \RuntimeException if the session is saved without being started, or if the session * is already closed */ @@ -97,6 +103,8 @@ public function save(); /** * Clear all session data in memory. + * + * @return void */ public function clear(); @@ -109,6 +117,8 @@ public function getBag(string $name): SessionBagInterface; /** * Registers a SessionBagInterface for use. + * + * @return void */ public function registerBag(SessionBagInterface $bag); diff --git a/src/Symfony/Component/HttpFoundation/StreamedJsonResponse.php b/src/Symfony/Component/HttpFoundation/StreamedJsonResponse.php new file mode 100644 index 0000000000000..445bd77d794cb --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/StreamedJsonResponse.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedJsonResponse represents a streamed HTTP response for JSON. + * + * A StreamedJsonResponse uses a structure and generics to create an + * efficient resource-saving JSON response. + * + * It is recommended to use flush() function after a specific number of items to directly stream the data. + * + * @see flush() + * + * @author Alexander Schranz + * + * Example usage: + * + * function loadArticles(): \Generator + * // some streamed loading + * yield ['title' => 'Article 1']; + * yield ['title' => 'Article 2']; + * yield ['title' => 'Article 3']; + * // recommended to use flush() after every specific number of items + * }), + * + * $response = new StreamedJsonResponse( + * // json structure with generators in which will be streamed + * [ + * '_embedded' => [ + * 'articles' => loadArticles(), // any generator which you want to stream as list of data + * ], + * ], + * ); + */ +class StreamedJsonResponse extends StreamedResponse +{ + private const PLACEHOLDER = '__symfony_json__'; + + /** + * @param mixed[] $data JSON Data containing PHP generators which will be streamed as list of data + * @param int $status The HTTP status code (200 "OK" by default) + * @param array $headers An array of HTTP headers + * @param int $encodingOptions Flags for the json_encode() function + */ + public function __construct( + private readonly array $data, + int $status = 200, + array $headers = [], + private int $encodingOptions = JsonResponse::DEFAULT_ENCODING_OPTIONS, + ) { + parent::__construct($this->stream(...), $status, $headers); + + if (!$this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + } + + private function stream(): void + { + $generators = []; + $structure = $this->data; + + array_walk_recursive($structure, function (&$item, $key) use (&$generators) { + if (self::PLACEHOLDER === $key) { + // if the placeholder is already in the structure it should be replaced with a new one that explode + // works like expected for the structure + $generators[] = $key; + } + + // generators should be used but for better DX all kind of Traversable and objects are supported + if (\is_object($item)) { + $generators[] = $item; + $item = self::PLACEHOLDER; + } elseif (self::PLACEHOLDER === $item) { + // if the placeholder is already in the structure it should be replaced with a new one that explode + // works like expected for the structure + $generators[] = $item; + } + }); + + $jsonEncodingOptions = \JSON_THROW_ON_ERROR | $this->encodingOptions; + $keyEncodingOptions = $jsonEncodingOptions & ~\JSON_NUMERIC_CHECK; + + $jsonParts = explode('"'.self::PLACEHOLDER.'"', json_encode($structure, $jsonEncodingOptions)); + + foreach ($generators as $index => $generator) { + // send first and between parts of the structure + echo $jsonParts[$index]; + + if ($generator instanceof \JsonSerializable || !$generator instanceof \Traversable) { + // the placeholders, JsonSerializable and none traversable items in the structure are rendered here + echo json_encode($generator, $jsonEncodingOptions); + + continue; + } + + $isFirstItem = true; + $startTag = '['; + + foreach ($generator as $key => $item) { + if ($isFirstItem) { + $isFirstItem = false; + // depending on the first elements key the generator is detected as a list or map + // we can not check for a whole list or map because that would hurt the performance + // of the streamed response which is the main goal of this response class + if (0 !== $key) { + $startTag = '{'; + } + + echo $startTag; + } else { + // if not first element of the generic, a separator is required between the elements + echo ','; + } + + if ('{' === $startTag) { + echo json_encode((string) $key, $keyEncodingOptions).':'; + } + + echo json_encode($item, $jsonEncodingOptions); + } + + echo '[' === $startTag ? ']' : '}'; + } + + // send last part of the structure + echo $jsonParts[array_key_last($jsonParts)]; + } +} diff --git a/src/Symfony/Component/HttpFoundation/StreamedResponse.php b/src/Symfony/Component/HttpFoundation/StreamedResponse.php index 0bddcdc9bb731..2c8ff15f3650e 100644 --- a/src/Symfony/Component/HttpFoundation/StreamedResponse.php +++ b/src/Symfony/Component/HttpFoundation/StreamedResponse.php @@ -59,17 +59,22 @@ public function setCallback(callable $callback): static /** * This method only sends the headers once. * + * @param null|positive-int $statusCode The status code to use, override the statusCode property if set and not null + * * @return $this */ - public function sendHeaders(): static + public function sendHeaders(/* int $statusCode = null */): static { if ($this->headersSent) { return $this; } - $this->headersSent = true; + $statusCode = \func_num_args() > 0 ? func_get_arg(0) : null; + if ($statusCode < 100 || $statusCode >= 200) { + $this->headersSent = true; + } - return parent::sendHeaders(); + return parent::sendHeaders($statusCode); } /** diff --git a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php index b3d375e4c37f9..417efc77a6688 100644 --- a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php +++ b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseCookieValueSame.php @@ -69,9 +69,7 @@ protected function getCookie(Response $response): ?Cookie { $cookies = $response->headers->getCookies(); - $filteredCookies = array_filter($cookies, function (Cookie $cookie) { - return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; - }); + $filteredCookies = array_filter($cookies, fn (Cookie $cookie) => $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain); return reset($filteredCookies) ?: null; } diff --git a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php index 9b15aeae83785..73393d386fbce 100644 --- a/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php +++ b/src/Symfony/Component/HttpFoundation/Test/Constraint/ResponseHasCookie.php @@ -61,9 +61,7 @@ private function getCookie(Response $response): ?Cookie { $cookies = $response->headers->getCookies(); - $filteredCookies = array_filter($cookies, function (Cookie $cookie) { - return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; - }); + $filteredCookies = array_filter($cookies, fn (Cookie $cookie) => $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain); return reset($filteredCookies) ?: null; } diff --git a/src/Symfony/Component/HttpFoundation/Tests/F 10000 ixtures/FooEnum.php b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/FooEnum.php new file mode 100644 index 0000000000000..a6f56fba1fffc --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/FooEnum.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Fixtures; + +enum FooEnum: int +{ + case Bar = 1; +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php index 696318e91ea98..6a447a39ccd23 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php @@ -12,11 +12,15 @@ namespace Symfony\Component\HttpFoundation\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\InputBag; +use Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum; class InputBagTest extends TestCase { + use ExpectDeprecationTrait; + public function testGet() { $bag = new InputBag(['foo' => 'bar', 'null' => null, 'int' => 1, 'float' => 1.0, 'bool' => false, 'stringable' => new class() implements \Stringable { @@ -35,6 +39,58 @@ public function __toString(): string $this->assertFalse($bag->get('bool'), '->get() gets the value of a bool parameter'); } + /** + * @group legacy + */ + public function testGetIntError() + { + $this->expectDeprecation('Since symfony/http-foundation 6.3: Ignoring invalid values when using "Symfony\Component\HttpFoundation\InputBag::getInt(\'foo\')" is deprecated and will throw a "Symfony\Component\HttpFoundation\Exception\BadRequestException" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.'); + + $bag = new InputBag(['foo' => 'bar']); + $result = $bag->getInt('foo'); + $this->assertSame(0, $result); + } + + /** + * @group legacy + */ + public function testGetBooleanError() + { + $this->expectDeprecation('Since symfony/http-foundation 6.3: Ignoring invalid values when using "Symfony\Component\HttpFoundation\InputBag::getBoolean(\'foo\')" is deprecated and will throw a "Symfony\Component\HttpFoundation\Exception\BadRequestException" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.'); + + $bag = new InputBag(['foo' => 'bar']); + $result = $bag->getBoolean('foo'); + $this->assertFalse($result); + } + + public function testGetString() + { + $bag = new InputBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class() implements \Stringable { + public function __toString(): string + { + return 'strval'; + } + }]); + + $this->assertSame('123', $bag->getString('integer'), '->getString() gets a value of parameter as string'); + $this->assertSame('abc', $bag->getString('string'), '->getString() gets a value of parameter as string'); + $this->assertSame('', $bag->getString('unknown'), '->getString() returns zero if a parameter is not defined'); + $this->assertSame('foo', $bag->getString('unknown', 'foo'), '->getString() returns the default if a parameter is not defined'); + $this->assertSame('1', $bag->getString('bool_true'), '->getString() returns "1" if a parameter is true'); + $this->assertSame('', $bag->getString('bool_false', 'foo'), '->getString() returns an empty empty string if a parameter is false'); + $this->assertSame('strval', $bag->getString('stringable'), '->getString() gets a value of a stringable paramater as string'); + } + + public function testGetStringExceptionWithArray() + { + $bag = new InputBag(['key' => ['abc']]); + + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('Input value "key" contains a non-scalar value.'); + + $bag->getString('key'); + } + public function testGetDoesNotUseDeepByDefault() { $bag = new InputBag(['foo' => ['bar' => 'moo']]); @@ -64,9 +120,7 @@ public function testFilterCallback() public function testFilterClosure() { $bag = new InputBag(['foo' => 'bar']); - $result = $bag->filter('foo', null, \FILTER_CALLBACK, ['options' => function ($value) { - return strtoupper($value); - }]); + $result = $bag->filter('foo', null, \FILTER_CALLBACK, ['options' => strtoupper(...)]); $this->assertSame('BAR', $result); } @@ -106,4 +160,55 @@ public function testFilterArrayWithoutArrayFlag() $bag = new InputBag(['foo' => ['bar', 'baz']]); $bag->filter('foo', \FILTER_VALIDATE_INT); } + + public function testGetEnum() + { + $bag = new InputBag(['valid-value' => 1]); + + $this->assertSame(FooEnum::Bar, $bag->getEnum('valid-value', FooEnum::class)); + } + + public function testGetEnumThrowsExceptionWithInvalidValue() + { + $bag = new InputBag(['invalid-value' => 2]); + + $this->expectException(BadRequestException::class); + if (\PHP_VERSION_ID >= 80200) { + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum.'); + } else { + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum "Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum".'); + } + + $this->assertNull($bag->getEnum('invalid-value', FooEnum::class)); + } + + public function testGetAlnumExceptionWithArray() + { + $bag = new InputBag(['word' => ['foo_BAR_012']]); + + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('Input value "word" contains a non-scalar value.'); + + $bag->getAlnum('word'); + } + + public function testGetAlphaExceptionWithArray() + { + $bag = new InputBag(['word' => ['foo_BAR_012']]); + + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('Input value "word" contains a non-scalar value.'); + + $bag->getAlpha('word'); + } + + public function testGetDigitsExceptionWithArray() + { + $bag = new InputBag(['word' => ['foo_BAR_012']]); + + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('Input value "word" contains a non-scalar value.'); + + $bag->getDigits('word'); + } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php index bde49e7a92d5a..8f6b869a19498 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php @@ -169,4 +169,54 @@ public static function getIp4SubnetMaskZeroData() [false, '1.2.3.4', '256.256.256/0'], // invalid CIDR notation ]; } + + /** + * @dataProvider getIsPrivateIpData + */ + public function testIsPrivateIp(string $ip, bool $matches) + { + $this->assertSame($matches, IpUtils::isPrivateIp($ip)); + } + + public static function getIsPrivateIpData(): array + { + return [ + // private + ['127.0.0.1', true], + ['10.0.0.1', true], + ['192.168.0.1', true], + ['172.16.0.1', true], + ['169.254.0.1', true], + ['0.0.0.1', true], + ['240.0.0.1', true], + ['::1', true], + ['fc00::1', true], + ['fe80::1', true], + ['::ffff:0:1', true], + ['fd00::1', true], + + // public + ['104.26.14.6', false], + ['2606:4700:20::681a:e06', false], + ]; + } + + public function testCacheSizeLimit() + { + $ref = new \ReflectionClass(IpUtils::class); + + /** @var array */ + $checkedIps = $ref->getStaticPropertyValue('checkedIps'); + $this->assertIsArray($checkedIps); + + $maxCheckedIps = 1000; + + for ($i = 1; $i < $maxCheckedIps * 1.5; ++$i) { + $ip = '192.168.1.'.str_pad((string) $i, 3, '0'); + + IpUtils::checkIp4($ip, '127.0.0.1'); + } + + $this->assertLessThan($maxCheckedIps, \count($checkedIps)); + } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php index 1b60fb2418008..62b95f42f4573 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php @@ -12,11 +12,15 @@ namespace Symfony\Component\HttpFoundation\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum; class ParameterBagTest extends TestCase { + use ExpectDeprecationTrait; + public function testConstructor() { $this->testAll(); @@ -111,34 +115,137 @@ public function testHas() public function testGetAlpha() { - $bag = new ParameterBag(['word' => 'foo_BAR_012']); + $bag = new ParameterBag(['word' => 'foo_BAR_012', 'bool' => true, 'integer' => 123]); - $this->assertEquals('fooBAR', $bag->getAlpha('word'), '->getAlpha() gets only alphabetic characters'); - $this->assertEquals('', $bag->getAlpha('unknown'), '->getAlpha() returns empty string if a parameter is not defined'); + $this->assertSame('fooBAR', $bag->getAlpha('word'), '->getAlpha() gets only alphabetic characters'); + $this->assertSame('', $bag->getAlpha('unknown'), '->getAlpha() returns empty string if a parameter is not defined'); + $this->assertSame('abcDEF', $bag->getAlpha('unknown', 'abc_DEF_012'), '->getAlpha() returns filtered default if a parameter is not defined'); + $this->assertSame('', $bag->getAlpha('integer', 'abc_DEF_012'), '->getAlpha() returns empty string if a parameter is an integer'); + $this->assertSame('', $bag->getAlpha('bool', 'abc_DEF_012'), '->getAlpha() returns empty string if a parameter is a boolean'); + } + + public function testGetAlphaExceptionWithArray() + { + $bag = new ParameterBag(['word' => ['foo_BAR_012']]); + + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Parameter value "word" cannot be converted to "string".'); + + $bag->getAlpha('word'); } public function testGetAlnum() { - $bag = new ParameterBag(['word' => 'foo_BAR_012']); + $bag = new ParameterBag(['word' => 'foo_BAR_012', 'bool' => true, 'integer' => 123]); + + $this->assertSame('fooBAR012', $bag->getAlnum('word'), '->getAlnum() gets only alphanumeric characters'); + $this->assertSame('', $bag->getAlnum('unknown'), '->getAlnum() returns empty string if a parameter is not defined'); + $this->assertSame('abcDEF012', $bag->getAlnum('unknown', 'abc_DEF_012'), '->getAlnum() returns filtered default if a parameter is not defined'); + $this->assertSame('123', $bag->getAlnum('integer', 'abc_DEF_012'), '->getAlnum() returns the number as string if a parameter is an integer'); + $this->assertSame('1', $bag->getAlnum('bool', 'abc_DEF_012'), '->getAlnum() returns 1 if a parameter is true'); + } + + public function testGetAlnumExceptionWithArray() + { + $bag = new ParameterBag(['word' => ['foo_BAR_012']]); - $this->assertEquals('fooBAR012', $bag->getAlnum('word'), '->getAlnum() gets only alphanumeric characters'); - $this->assertEquals('', $bag->getAlnum('unknown'), '->getAlnum() returns empty string if a parameter is not defined'); + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Parameter value "word" cannot be converted to "string".'); + + $bag->getAlnum('word'); } public function testGetDigits() { - $bag = new ParameterBag(['word' => 'foo_BAR_012']); + $bag = new ParameterBag(['word' => 'foo_BAR_0+1-2', 'bool' => true, 'integer' => 123]); + + $this->assertSame('012', $bag->getDigits('word'), '->getDigits() gets only digits as string'); + $this->assertSame('', $bag->getDigits('unknown'), '->getDigits() returns empty string if a parameter is not defined'); + $this->assertSame('012', $bag->getDigits('unknown', 'abc_DEF_012'), '->getDigits() returns filtered default if a parameter is not defined'); + $this->assertSame('123', $bag->getDigits('integer', 'abc_DEF_012'), '->getDigits() returns the number as string if a parameter is an integer'); + $this->assertSame('1', $bag->getDigits('bool', 'abc_DEF_012'), '->getDigits() returns 1 if a parameter is true'); + } + + public function testGetDigitsExceptionWithArray() + { + $bag = new ParameterBag(['word' => ['foo_BAR_012']]); - $this->assertEquals('012', $bag->getDigits('word'), '->getDigits() gets only digits as string'); - $this->assertEquals('', $bag->getDigits('unknown'), '->getDigits() returns empty string if a parameter is not defined'); + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Parameter value "word" cannot be converted to "string".'); + + $bag->getDigits('word'); } public function testGetInt() { - $bag = new ParameterBag(['digits' => '0123']); + $bag = new ParameterBag(['digits' => '123', 'bool' => true]); + + $this->assertSame(123, $bag->getInt('digits', 0), '->getInt() gets a value of parameter as integer'); + $this->assertSame(0, $bag->getInt('unknown', 0), '->getInt() returns zero if a parameter is not defined'); + $this->assertSame(10, $bag->getInt('unknown', 10), '->getInt() returns the default if a parameter is not defined'); + $this->assertSame(1, $bag->getInt('bool', 0), '->getInt() returns 1 if a parameter is true'); + } + + /** + * @group legacy + */ + public function testGetIntExceptionWithArray() + { + $this->expectDeprecation('Since symfony/http-foundation 6.3: Ignoring invalid values when using "Symfony\Component\HttpFoundation\ParameterBag::getInt(\'digits\')" is deprecated and will throw an "UnexpectedValueException" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.'); + + $bag = new ParameterBag(['digits' => ['123']]); + $result = $bag->getInt('digits', 0); + $this->assertSame(0, $result); + } + + /** + * @group legacy + */ + public function testGetIntExceptionWithInvalid() + { + $this->expectDeprecation('Since symfony/http-foundation 6.3: Ignoring invalid values when using "Symfony\Component\HttpFoundation\ParameterBag::getInt(\'word\')" is deprecated and will throw an "UnexpectedValueException" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.'); + + $bag = new ParameterBag(['word' => 'foo_BAR_012']); + $result = $bag->getInt('word', 0); + $this->assertSame(0, $result); + } + + public function testGetString() + { + $bag = new ParameterBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class() implements \Stringable { + public function __toString(): string + { + return 'strval'; + } + }]); + + $this->assertSame('123', $bag->getString('integer'), '->getString() gets a value of parameter as string'); + $this->assertSame('abc', $bag->getString('string'), '->getString() gets a value of parameter as string'); + $this->assertSame('', $bag->getString('unknown'), '->getString() returns zero if a parameter is not defined'); + $this->assertSame('foo', $bag->getString('unknown', 'foo'), '->getString() returns the default if a parameter is not defined'); + $this->assertSame('1', $bag->getString('bool_true'), '->getString() returns "1" if a parameter is true'); + $this->assertSame('', $bag->getString('bool_false', 'foo'), '->getString() returns an empty empty string if a parameter is false'); + $this->assertSame('strval', $bag->getString('stringable'), '->getString() gets a value of a stringable paramater as string'); + } + + public function testGetStringExceptionWithArray() + { + $bag = new ParameterBag(['key' => ['abc']]); + + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Parameter value "key" cannot be converted to "string".'); - $this->assertEquals(123, $bag->getInt('digits'), '->getInt() gets a value of parameter as integer'); - $this->assertEquals(0, $bag->getInt('unknown'), '->getInt() returns zero if a parameter is not defined'); + $bag->getString('key'); + } + + public function testGetStringExceptionWithObject() + { + $bag = new ParameterBag(['object' => $this]); + + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Parameter value "object" cannot be converted to "string".'); + + $bag->getString('object'); } public function testFilter() @@ -163,13 +270,13 @@ public function testFilter() // This test is repeated for code-coverage $this->assertEquals('http://example.com/foo', $bag->filter('url', '', \FILTER_VALIDATE_URL, \FILTER_FLAG_PATH_REQUIRED), '->filter() gets a value of parameter as URL with a path'); - $this->assertFalse($bag->filter('dec', '', \FILTER_VALIDATE_INT, [ - 'flags' => \FILTER_FLAG_ALLOW_HEX, + $this->assertNull($bag->filter('dec', '', \FILTER_VALIDATE_INT, [ + 'flags' => \FILTER_FLAG_ALLOW_HEX | \FILTER_NULL_ON_FAILURE, 'options' => ['min_range' => 1, 'max_range' => 0xFF], ]), '->filter() gets a value of parameter as integer between boundaries'); - $this->assertFalse($bag->filter('hex', '', \FILTER_VALIDATE_INT, [ - 'flags' => \FILTER_FLAG_ALLOW_HEX, + $this->assertNull($bag->filter('hex', '', \FILTER_VALIDATE_INT, [ + 'flags' => \FILTER_FLAG_ALLOW_HEX | \FILTER_NULL_ON_FAILURE, 'options' => ['min_range' => 1, 'max_range' => 0xFF], ]), '->filter() gets a value of parameter as integer between boundaries'); @@ -188,9 +295,7 @@ public function testFilterCallback() public function testFilterClosure() { $bag = new ParameterBag(['foo' => 'bar']); - $result = $bag->filter('foo', null, \FILTER_CALLBACK, ['options' => function ($value) { - return strtoupper($value); - }]); + $result = $bag->filter('foo', null, \FILTER_CALLBACK, ['options' => strtoupper(...)]); $this->assertSame('BAR', $result); } @@ -219,11 +324,70 @@ public function testCount() public function testGetBoolean() { - $parameters = ['string_true' => 'true', 'string_false' => 'false']; + $parameters = ['string_true' => 'true', 'string_false' => 'false', 'string' => 'abc']; $bag = new ParameterBag($parameters); $this->assertTrue($bag->getBoolean('string_true'), '->getBoolean() gets the string true as boolean true'); $this->assertFalse($bag->getBoolean('string_false'), '->getBoolean() gets the string false as boolean false'); $this->assertFalse($bag->getBoolean('unknown'), '->getBoolean() returns false if a parameter is not defined'); + $this->assertTrue($bag->getBoolean('unknown', true), '->getBoolean() returns default if a parameter is not defined'); + } + + /** + * @group legacy + */ + public function testGetBooleanExceptionWithInvalid() + { + $this->expectDeprecation('Since symfony/http-foundation 6.3: Ignoring invalid values when using "Symfony\Component\HttpFoundation\ParameterBag::getBoolean(\'invalid\')" is deprecated and will throw an "UnexpectedValueException" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.'); + + $bag = new ParameterBag(['invalid' => 'foo']); + $result = $bag->getBoolean('invalid', 0); + $this->assertFalse($result); + } + + public function testGetEnum() + { + $bag = new ParameterBag(['valid-value' => 1]); + + $this->assertSame(FooEnum::Bar, $bag->getEnum('valid-value', FooEnum::class)); + + $this->assertNull($bag->getEnum('invalid-key', FooEnum::class)); + $this->assertSame(FooEnum::Bar, $bag->getEnum('invalid-key', FooEnum::class, FooEnum::Bar)); + } + + public function testGetEnumThrowsExceptionWithNotBackingValue() + { + $bag = new ParameterBag(['invalid-value' => 2]); + + $this->expectException(\UnexpectedValueException::class); + if (\PHP_VERSION_ID >= 80200) { + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum.'); + } else { + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: 2 is not a valid backing value for enum "Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum".'); + } + + $this->assertNull($bag->getEnum('invalid-value', FooEnum::class)); + } + + public function testGetEnumThrowsExceptionWithInvalidValueType() + { + $bag = new ParameterBag(['invalid-value' => ['foo']]); + + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Parameter "invalid-value" cannot be converted to enum: Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum::from(): Argument #1 ($value) must be of type int, array given.'); + + $this->assertNull($bag->getEnum('invalid-value', FooEnum::class)); + } +} + +class InputStringable +{ + public function __construct(private string $value) + { + } + + public function __toString(): string + { + return $this->value; } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/AttributesRequestMatcherTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/AttributesRequestMatcherTest.php index 3f4fd39c3a3c6..dcb2d0b9880dd 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/AttributesRequestMatcherTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/AttributesRequestMatcherTest.php @@ -26,9 +26,7 @@ public function test(string $key, string $regexp, bool $expected) $matcher = new AttributesRequestMatcher([$key => $regexp]); $request = Request::create('/admin/foo'); $request->attributes->set('foo', 'foo_bar'); - $request->attributes->set('_controller', function () { - return new Response('foo'); - }); + $request->attributes->set('_controller', fn () => new Response('foo')); $this->assertSame($expected, $matcher->matches($request)); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php index 5e028fc4a809c..cda2b1f2334d6 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php @@ -173,9 +173,7 @@ public function testAttributesWithClosure() $matcher = new RequestMatcher(); $request = Request::create('/admin/foo'); - $request->attributes->set('_controller', function () { - return new Response('foo'); - }); + $request->attributes->set('_controller', fn () => new Response('foo')); $matcher->matchAttribute('_controller', 'babar'); $this->assertFalse($matcher->matches($request)); diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 1ed4e5fccf508..5e8df28bb5713 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1300,6 +1300,17 @@ public function testToArray() $this->assertEquals(['foo' => 'bar'], $req->toArray()); } + public function testGetPayload() + { + $req = new Request([], [], [], [], [], [], json_encode(['foo' => 'bar'])); + $this->assertSame(['foo' => 'bar'], $req->getPayload()->all()); + $req->getPayload()->set('new', 'key'); + $this->assertSame(['foo' => 'bar'], $req->getPayload()->all()); + + $req = new Request([], ['foo' => 'bar'], [], [], [], [], json_encode(['baz' => 'qux'])); + $this->assertSame(['foo' => 'bar'], $req->getPayload()->all()); + } + /** * @dataProvider provideOverloadedMethods */ @@ -2136,9 +2147,7 @@ public function testSetTrustedHostsDoesNotBreakOnSpecialCharacters() public function testFactory() { - Request::setFactory(function (array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { - return new NewRequest(); - }); + Request::setFactory(fn (array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) => new NewRequest()); $this->assertEquals('foo', Request::create('/')->getFoo()); @@ -2556,6 +2565,15 @@ public function testReservedFlags() $this->assertNotSame(0b10000000, $value, sprintf('The constant "%s" should not use the reserved value "0b10000000".', $constant)); } } + + /** + * @group legacy + */ + public function testInvalidUriCreationDeprecated() + { + $this->expectDeprecation('Since symfony/http-foundation 6.3: Calling "Symfony\Component\HttpFoundation\Request::create()" with an invalid URI is deprecated.'); + Request::create('/invalid-path:123'); + } } class RequestContentProxy extends Request diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php index 5cffa87d04894..841b7a50fa3c8 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php @@ -44,7 +44,7 @@ public static function tearDownAfterClass(): void public function testCookie($fixture) { $result = file_get_contents(sprintf('http://localhost:8054/%s.php', $fixture)); - $result = preg_replace_callback('/expires=[^;]++/', function ($m) { return str_replace('-', ' ', $m[0]); }, $result); + $result = preg_replace_callback('/expires=[^;]++/', fn ($m) => str_replace('-', ' ', $m[0]), $result); $this->assertStringMatchesFormatFile(__DIR__.sprintf('/Fixtures/response-functional/%s.expected', $fixture), $result); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php index 7ab060ec19142..bf126489d45aa 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php @@ -42,6 +42,17 @@ public function testSendHeaders() $this->assertSame($response, $headers); } + public function testSendInformationalResponse() + { + $response = new Response(); + $response->sendHeaders(103); + + // Informational responses must not override the main status code + $this->assertSame(200, $response->getStatusCode()); + + $response->sendHeaders(); + } + public function testSend() { $response = new Response(); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php index 0e13f13fb11da..52f8a4cb025b4 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; use PHPUnit\Framework\TestCase; +use Relay\Relay; use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler; /** @@ -33,7 +34,7 @@ abstract class AbstractRedisSessionHandlerTestCase extends TestCase */ protected $redisClient; - abstract protected function createRedisClient(string $host): \Redis|\RedisArray|\RedisCluster|\Predis\Client; + abstract protected function createRedisClient(string $host): \Redis|Relay|\RedisArray|\RedisCluster|\Predis\Client; protected function setUp(): void { diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php index 8e42f84276e58..fa5119cf3b8c7 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php @@ -46,7 +46,7 @@ public function testSession($fixture) $context = ['http' => ['header' => "Cookie: sid=123abc\r\n"]]; $context = stream_context_create($context); $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context); - $result = preg_replace_callback('/expires=[^;]++/', function ($m) { return str_replace('-', ' ', $m[0]); }, $result); + $result = preg_replace_callback('/expires=[^;]++/', fn ($m) => str_replace('-', ' ', $m[0]), $result); $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/invalid_regenerate.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/invalid_regenerate.php index 2798442a9d624..d7ec890d99e61 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/invalid_regenerate.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/invalid_regenerate.php @@ -17,4 +17,4 @@ echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty'; echo "\n"; -ob_start(function ($buffer) { return preg_replace('~_sf2_meta.*$~m', '', str_replace(session_id(), 'random_session_id', $buffer)); }); +ob_start(fn ($buffer) => preg_replace('~_sf2_meta.*$~m', '', str_replace(session_id(), 'random_session_id', $buffer))); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php index a0f635c8712ec..b85849595ad0b 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php @@ -7,4 +7,4 @@ session_regenerate_id(true); -ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); }); +ob_start(fn ($buffer) => str_replace(session_id(), 'random_session_id', $buffer)); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php index 96dca3c2c0006..a86c8205623f9 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php @@ -21,4 +21,4 @@ echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty'; -ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); }); +ob_start(fn ($buffer) => str_replace(session_id(), 'random_session_id', $buffer)); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php index fc2c4182895ac..a005362ceba5a 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite.php @@ -10,4 +10,4 @@ $_SESSION = ['foo' => 'bar']; -ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); }); +ob_start(fn ($buffer) => str_replace(session_id(), 'random_session_id', $buffer)); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php index a28b6fedfc375..13c951ee32e0a 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_samesite_and_migration.php @@ -12,4 +12,4 @@ $storage->regenerate(true); -ob_start(function ($buffer) { return preg_replace('~_sf2_meta.*$~m', '', str_replace(session_id(), 'random_session_id', $buffer)); }); +ob_start(fn ($buffer) => preg_replace('~_sf2_meta.*$~m', '', str_replace(session_id(), 'random_session_id', $buffer))); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php index d5e84d181f780..0b25c37d90638 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php @@ -40,7 +40,7 @@ protected function setUp(): void } $r = new \ReflectionClass(\Memcached::class); - $methodsToMock = array_map(function ($m) { return $m->name; }, $r->getMethods(\ReflectionMethod::IS_PUBLIC)); + $methodsToMock = array_map(fn ($m) => $m->name, $r->getMethods(\ReflectionMethod::IS_PUBLIC)); $methodsToMock = array_diff($methodsToMock, ['getDelayed', 'getDelayedByKey']); $this->memcached = $this->getMockBuilder(\Memcached::class) diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php index eb6842a5e3bb3..ce8e778749222 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; +use Doctrine\DBAL\Schema\Schema; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; @@ -156,9 +157,7 @@ public function testReadLockedConvertsStreamToString() $selectStmt = $this->createMock(\PDOStatement::class); $insertStmt = $this->createMock(\PDOStatement::class); - $pdo->prepareResult = function ($statement) use ($selectStmt, $insertStmt) { - return str_starts_with($statement, 'INSERT') ? $insertStmt : $selectStmt; - }; + $pdo->prepareResult = fn ($statement) => str_starts_with($statement, 'INSERT') ? $insertStmt : $selectStmt; $content = 'foobar'; $stream = $this->createStream($content); @@ -327,6 +326,35 @@ public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPa } } + public function testConfigureSchemaDifferentDatabase() + { + $schema = new Schema(); + + $pdoSessionHandler = new PdoSessionHandler($this->getMemorySqlitePdo()); + $pdoSessionHandler->configureSchema($schema, fn () => false); + $this->assertFalse($schema->hasTable('sessions')); + } + + public function testConfigureSchemaSameDatabase() + { + $schema = new Schema(); + + $pdoSessionHandler = new PdoSessionHandler($this->getMemorySqlitePdo()); + $pdoSessionHandler->configureSchema($schema, fn () => true); + $this->assertTrue($schema->hasTable('sessions')); + } + + public function testConfigureSchemaTableExistsPdo() + { + $schema = new Schema(); + $schema->createTable('sessions'); + + $pdoSessionHandler = new PdoSessionHandler($this->getMemorySqlitePdo()); + $pdoSessionHandler->configureSchema($schema, fn () => true); + $table = $schema->getTable('sessions'); + $this->assertEmpty($table->getColumns(), 'The table was not overwritten'); + } + public static function provideUrlDsnPairs() { yield ['mysql://localhost/test', 'mysql:host=localhost;dbname=test;']; diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php index fd4a13bef5fdf..492487766b2f5 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php @@ -20,6 +20,9 @@ class PredisClusterSessionHandlerTest extends AbstractRedisSessionHandlerTestCas { protected function createRedisClient(string $host): Client { - return new Client([array_combine(['host', 'port'], explode(':', getenv('REDIS_HOST')) + [1 => 6379])]); + return new Client( + [array_combine(['host', 'port'], explode(':', getenv('REDIS_HOST')) + [1 => 6379])], + ['cluster' => 'redis'] + ); } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php new file mode 100644 index 0000000000000..76553f96d3375 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Session\Storage\Handler; + +use Relay\Relay; +use Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler\AbstractRedisSessionHandlerTestCase; + +/** + * @requires extension relay + * + * @group integration + */ +class RelaySessionHandlerTest extends AbstractRedisSessionHandlerTestCase +{ + protected function createRedisClient(string $host): Relay + { + return new Relay(...explode(':', $host)); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php index e91f06a923738..41699cf5631b5 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/SessionHandlerFactoryTest.php @@ -73,7 +73,7 @@ public function testCreateRedisHandlerFromDsn() $ttlProperty = $reflection->getProperty('ttl'); $this->assertSame(3600, $ttlProperty->getValue($handler)); - $handler = SessionHandlerFactory::createHandler('redis://localhost?prefix=foo&ttl=3600&ignored=bar', ['ttl' => function () { return 123; }]); + $handler = SessionHandlerFactory::createHandler('redis://localhost?prefix=foo&ttl=3600&ignored=bar', ['ttl' => fn () => 123]); $this->assertInstanceOf(\Closure::class, $reflection->getProperty('ttl')->getValue($handler)); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/StreamedJsonResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/StreamedJsonResponseTest.php new file mode 100644 index 0000000000000..e142672fd0658 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/StreamedJsonResponseTest.php @@ -0,0 +1,241 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\StreamedJsonResponse; + +class StreamedJsonResponseTest extends TestCase +{ + public function testResponseSimpleList() + { + $content = $this->createSendResponse( + [ + '_embedded' => [ + 'articles' => $this->generatorSimple('Article'), + 'news' => $this->generatorSimple('News'), + ], + ], + ); + + $this->assertSame('{"_embedded":{"articles":["Article 1","Article 2","Article 3"],"news":["News 1","News 2","News 3"]}}', $content); + } + + public function testResponseObjectsList() + { + $content = $this->createSendResponse( + [ + '_embedded' => [ + 'articles' => $this->generatorArray('Article'), + ], + ], + ); + + $this->assertSame('{"_embedded":{"articles":[{"title":"Article 1"},{"title":"Article 2"},{"title":"Article 3"}]}}', $content); + } + + public function testResponseWithoutGenerator() + { + // while it is not the intended usage, all kind of iterables should be supported for good DX + $content = $this->createSendResponse( + [ + '_embedded' => [ + 'articles' => ['Article 1', 'Article 2', 'Article 3'], + ], + ], + ); + + $this->assertSame('{"_embedded":{"articles":["Article 1","Article 2","Article 3"]}}', $content); + } + + public function testResponseWithPlaceholder() + { + // the placeholder must not conflict with generator injection + $content = $this->createSendResponse( + [ + '_embedded' => [ + 'articles' => $this->generatorArray('Article'), + 'placeholder' => '__symfony_json__', + 'news' => $this->generatorSimple('News'), + ], + 'placeholder' => '__symfony_json__', + ], + ); + + $this->assertSame('{"_embedded":{"articles":[{"title":"Article 1"},{"title":"Article 2"},{"title":"Article 3"}],"placeholder":"__symfony_json__","news":["News 1","News 2","News 3"]},"placeholder":"__symfony_json__"}', $content); + } + + public function testResponseWithMixedKeyType() + { + $content = $this->createSendResponse( + [ + '_embedded' => [ + 'list' => (function (): \Generator { + yield 0 => 'test'; + yield 'key' => 'value'; + })(), + 'map' => (function (): \Generator { + yield 'key' => 'value'; + yield 0 => 'test'; + })(), + 'integer' => (function (): \Generator { + yield 1 => 'one'; + yield 3 => 'three'; + })(), + ], + ] + ); + + $this->assertSame('{"_embedded":{"list":["test","value"],"map":{"key":"value","0":"test"},"integer":{"1":"one","3":"three"}}}', $content); + } + + public function testResponseOtherTraversable() + { + $arrayObject = new \ArrayObject(['__symfony_json__' => '__symfony_json__']); + + $iteratorAggregate = new class() implements \IteratorAggregate { + public function getIterator(): \Traversable + { + return new \ArrayIterator(['__symfony_json__']); + } + }; + + $jsonSerializable = new class() implements \IteratorAggregate, \JsonSerializable { + public function getIterator(): \Traversable + { + return new \ArrayIterator(['This should be ignored']); + } + + public function jsonSerialize(): mixed + { + return ['__symfony_json__' => '__symfony_json__']; + } + }; + + // while Generators should be used for performance reasons, the object should also work with any Traversable + // to make things easier for a developer + $content = $this->createSendResponse( + [ + 'arrayObject' => $arrayObject, + 'iteratorAggregate' => $iteratorAggregate, + 'jsonSerializable' => $jsonSerializable, + // add a Generator to make sure it still work in combination with other Traversable objects + 'articles' => $this->generatorArray('Article'), + ], + ); + + $this->assertSame('{"arrayObject":{"__symfony_json__":"__symfony_json__"},"iteratorAggregate":["__symfony_json__"],"jsonSerializable":{"__symfony_json__":"__symfony_json__"},"articles":[{"title":"Article 1"},{"title":"Article 2"},{"title":"Article 3"}]}', $content); + } + + public function testPlaceholderAsKeyAndValueInStructure() + { + $content = $this->createSendResponse( + [ + '__symfony_json__' => '__symfony_json__', + 'articles' => $this->generatorArray('Article'), + ], + ); + + $this->assertSame('{"__symfony_json__":"__symfony_json__","articles":[{"title":"Article 1"},{"title":"Article 2"},{"title":"Article 3"}]}', $content); + } + + public function testResponseStatusCode() + { + $response = new StreamedJsonResponse([], 201); + + $this->assertSame(201, $response->getStatusCode()); + } + + public function testPlaceholderAsObjectStructure() + { + $object = new class() { + public $__symfony_json__ = 'foo'; + public $bar = '__symfony_json__'; + }; + + $content = $this->createSendResponse( + [ + 'object' => $object, + // add a Generator to make sure it still work in combination with other object holding placeholders + 'articles' => $this->generatorArray('Article'), + ], + ); + + $this->assertSame('{"object":{"__symfony_json__":"foo","bar":"__symfony_json__"},"articles":[{"title":"Article 1"},{"title":"Article 2"},{"title":"Article 3"}]}', $content); + } + + public function testResponseHeaders() + { + $response = new StreamedJsonResponse([], 200, ['X-Test' => 'Test']); + + $this->assertSame('Test', $response->headers->get('X-Test')); + } + + public function testCustomContentType() + { + $response = new StreamedJsonResponse([], 200, ['Content-Type' => 'application/json+stream']); + + $this->assertSame('application/json+stream', $response->headers->get('Content-Type')); + } + + public function testEncodingOptions() + { + $response = new StreamedJsonResponse([ + '_embedded' => [ + 'count' => '2', // options are applied to the initial json encode + 'values' => (function (): \Generator { + yield 'with/unescaped/slash' => 'With/a/slash'; // options are applied to key and values + yield '3' => '3'; // numeric check for value, but not for the key + })(), + ], + ], encodingOptions: \JSON_UNESCAPED_SLASHES | \JSON_NUMERIC_CHECK); + + ob_start(); + $response->send(); + $content = ob_get_clean(); + + $this->assertSame('{"_embedded":{"count":2,"values":{"with/unescaped/slash":"With/a/slash","3":3}}}', $content); + } + + /** + * @param mixed[] $data + */ + private function createSendResponse(array $data): string + { + $response = new StreamedJsonResponse($data); + + ob_start(); + $response->send(); + + return ob_get_clean(); + } + + /** + * @return \Generator + */ + private function generatorSimple(string $test): \Generator + { + yield $test.' 1'; + yield $test.' 2'; + yield $test.' 3'; + } + + /** + * @return \Generator + */ + private function generatorArray(string $test): \Generator + { + yield ['title' => $test.' 1']; + yield ['title' => $test.' 2']; + yield ['title' => $test.' 3']; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/StreamedResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/StreamedResponseTest.php index 1ca1bb92ae377..2a2b7e7318b2e 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/StreamedResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/StreamedResponseTest.php @@ -124,4 +124,15 @@ public function testSetNotModified() $string = ob_get_clean(); $this->assertEmpty($string); } + + public function testSendInformationalResponse() + { + $response = new StreamedResponse(); + $response->sendHeaders(103); + + // Informational responses must not override the main status code + $this->assertSame(200, $response->getStatusCode()); + + $response->sendHeaders(); + } } diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json index e333a23b7ab31..248bcbb163c39 100644 --- a/src/Symfony/Component/HttpFoundation/composer.json +++ b/src/Symfony/Component/HttpFoundation/composer.json @@ -17,11 +17,13 @@ ], "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.1" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" }, "require-dev": { - "predis/predis": "~1.0", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1|^2.0", "symfony/cache": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", @@ -32,9 +34,6 @@ "conflict": { "symfony/cache": "<6.2" }, - "suggest" : { - "symfony/mime": "To use the file extension guesser" - }, "autoload": { "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/HttpKernel/Attribute/AsTargetedValueResolver.php b/src/Symfony/Component/HttpKernel/Attribute/AsTargetedValueResolver.php new file mode 100644 index 0000000000000..c58f0e6dd596c --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Attribute/AsTargetedValueResolver.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +/** + * Service tag to autoconfigure targeted value resolvers. + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsTargetedValueResolver +{ + public function __construct( + public readonly ?string $name = null, + ) { + } +} diff --git a/src/Symfony/Component/HttpKernel/Attribute/MapDateTime.php b/src/Symfony/Component/HttpKernel/Attribute/MapDateTime.php index ce9f8568553dc..bfe48a809095d 100644 --- a/src/Symfony/Component/HttpKernel/Attribute/MapDateTime.php +++ b/src/Symfony/Component/HttpKernel/Attribute/MapDateTime.php @@ -11,14 +11,19 @@ namespace Symfony\Component\HttpKernel\Attribute; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver; + /** * Controller parameter tag to configure DateTime arguments. */ #[\Attribute(\Attribute::TARGET_PARAMETER)] -class MapDateTime +class MapDateTime extends ValueResolver { public function __construct( - public readonly ?string $format = null + public readonly ?string $format = null, + bool $disabled = false, + string $resolver = DateTimeValueResolver::class, ) { + parent::__construct($resolver, $disabled); } } diff --git a/src/Symfony/Component/HttpKernel/Attribute/MapQueryParameter.php b/src/Symfony/Component/HttpKernel/Attribute/MapQueryParameter.php new file mode 100644 index 0000000000000..f83e331e4118f --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Attribute/MapQueryParameter.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\QueryParameterValueResolver; + +/** + * Can be used to pass a query parameter to a controller argument. + * + * @author Ruud Kamphuis + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +final class MapQueryParameter extends ValueResolver +{ + /** + * @see https://php.net/filter.filters.validate for filter, flags and options + * + * @param string|null $name The name of the query parameter. If null, the name of the argument in the controller will be used. + */ + public function __construct( + public ?string $name = null, + public ?int $filter = null, + public int $flags = 0, + public array $options = [], + string $resolver = QueryParameterValueResolver::class, + ) { + parent::__construct($resolver); + } +} diff --git a/src/Symfony/Component/HttpKernel/Attribute/MapQueryString.php b/src/Symfony/Component/HttpKernel/Attribute/MapQueryString.php new file mode 100644 index 0000000000000..f3717b31d0628 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Attribute/MapQueryString.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; +use Symfony\Component\Validator\Constraints\GroupSequence; + +/** + * Controller parameter tag to map the query string of the request to typed object and validate it. + * + * @author Konstantin Myakshin + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapQueryString extends ValueResolver +{ + public function __construct( + public readonly array $serializationContext = [], + public readonly string|GroupSequence|array|null $validationGroups = null, + string $resolver = RequestPayloadValueResolver::class, + ) { + parent::__construct($resolver); + } +} diff --git a/src/Symfony/Component/HttpKernel/Attribute/MapRequestPayload.php b/src/Symfony/Component/HttpKernel/Attribute/MapRequestPayload.php new file mode 100644 index 0000000000000..f02414343f0d1 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Attribute/MapRequestPayload.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\HttpKernel\Attribute; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver; +use Symfony\Component\Validator\Constraints\GroupSequence; + +/** + * Controller parameter tag to map the request content to typed object and validate it. + * + * @author Konstantin Myakshin + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapRequestPayload extends ValueResolver +{ + public function __construct( + public readonly array|string|null $acceptFormat = null, + public readonly array $serializationContext = [], + public readonly string|GroupSequence|array|null $validationGroups = null, + string $resolver = RequestPayloadValueResolver::class, + ) { + parent::__construct($resolver); + } +} diff --git a/src/Symfony/Component/HttpKernel/Attribute/ValueResolver.php b/src/Symfony/Component/HttpKernel/Attribute/ValueResolver.php new file mode 100644 index 0000000000000..5875a27484ba7 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Attribute/ValueResolver.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; + +#[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::IS_REPEATABLE)] +class ValueResolver +{ + /** + * @param class-string|string $resolver + */ + public function __construct( + public string $resolver, + public bool $disabled = false, + ) { + } +} diff --git a/src/Symfony/Component/HttpKernel/Attribute/WithHttpStatus.php b/src/Symfony/Component/HttpKernel/Attribute/WithHttpStatus.php new file mode 100644 index 0000000000000..718427aacc761 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Attribute/WithHttpStatus.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +/** + * @author Dejan Angelov + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class WithHttpStatus +{ + /** + * @param array $headers + */ + public function __construct( + public readonly int $statusCode, + public readonly array $headers = [], + ) { + } +} diff --git a/src/Symfony/Component/HttpKernel/Attribute/WithLogLevel.php b/src/Symfony/Component/HttpKernel/Attribute/WithLogLevel.php new file mode 100644 index 0000000000000..762b077043ae2 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Attribute/WithLogLevel.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Attribute; + +use Psr\Log\LogLevel; + +/** + * @author Dejan Angelov + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +final class WithLogLevel +{ + /** + * @param LogLevel::* $level + */ + public function __construct(public readonly string $level) + { + if (!\defined('Psr\Log\LogLevel::'.strtoupper($this->level))) { + throw new \InvalidArgumentException(sprintf('Invalid log level "%s".', $this->level)); + } + } +} diff --git a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php index 4d17cf5db32df..2ddf55f2cb762 100644 --- a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php +++ b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php @@ -31,10 +31,16 @@ abstract class Bundle implements BundleInterface protected $path; private string $namespace; + /** + * @return void + */ public function boot() { } + /** + * @return void + */ public function shutdown() { } @@ -42,6 +48,8 @@ public function shutdown() /** * This method can be overridden to register compilation passes, * other extensions, ... + * + * @return void */ public function build(ContainerBuilder $container) { @@ -110,6 +118,9 @@ final public function getName(): string return $this->name; } + /** + * @return void + */ public function registerCommands(Application $application) { } @@ -132,7 +143,7 @@ protected function createContainerExtension(): ?ExtensionInterface return class_exists($class = $this->getContainerExtensionClass()) ? new $class() : null; } - private function parseClassName() + private function parseClassName(): void { $pos = strrpos(static::class, '\\'); $this->namespace = false === $pos ? '' : substr(static::class, 0, $pos); diff --git a/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php b/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php index 5490632552df6..02cb9641db053 100644 --- a/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php +++ b/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php @@ -24,11 +24,15 @@ interface BundleInterface extends ContainerAwareInterface { /** * Boots the Bundle. + * + * @return void */ public function boot(); /** * Shutdowns the Bundle. + * + * @return void */ public function shutdown(); @@ -36,6 +40,8 @@ public function shutdown(); * Builds the bundle. * * It is only ever called once when the cache is empty. + * + * @return void */ public function build(ContainerBuilder $container); diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 2a5aad623bfeb..8aceb90c862f7 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -1,6 +1,21 @@ CHANGELOG ========= +6.3 +--- + + * Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead + * `FileProfilerStorage` removes profiles automatically after two days + * Add `#[WithHttpStatus]` for defining status codes for exceptions + * Use an instance of `Psr\Clock\ClockInterface` to generate the current date time in `DateTimeValueResolver` + * Add `#[WithLogLevel]` for defining log levels for exceptions + * Add `skip_response_headers` to the `HttpCache` options + * Introduce targeted value resolvers with `#[ValueResolver]` and `#[AsTargetedValueResolver]` + * Add `#[MapRequestPayload]` to map and validate request payload from `Request::getContent()` or `Request::$request->all()` to typed objects + * Add `#[MapQueryString]` to map and validate request query string from `Request::$query->all()` to typed objects + * Add `#[MapQueryParameter]` to map and validate individual query parameters to controller arguments + * Collect data from every event dispatcher + 6.2 --- diff --git a/src/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php b/src/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php index 270f690e5b839..5ca426562402e 100644 --- a/src/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php +++ b/src/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php @@ -20,6 +20,8 @@ interface CacheClearerInterface { /** * Clears any caches necessary. + * + * @return void */ public function clear(string $cacheDir); } diff --git a/src/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php b/src/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php index 8ce77e 10000 2b74293..0c541f21b858b 100644 --- a/src/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php +++ b/src/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php @@ -30,7 +30,7 @@ public function __construct(iterable $clearers = []) $this->clearers = $clearers; } - public function clear(string $cacheDir) + public function clear(string $cacheDir): void { foreach ($this->clearers as $clearer) { $clearer->clear($cacheDir); diff --git a/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php b/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php index 054f77bc22b3e..3c99b74af3060 100644 --- a/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php +++ b/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php @@ -57,6 +57,9 @@ public function clearPool(string $name): bool return $this->pools[$name]->clear(); } + /** + * @return void + */ public function clear(string $cacheDir) { foreach ($this->pools as $pool) { diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php index aef42d62f4265..f940ba4a7233a 100644 --- a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php +++ b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php @@ -18,6 +18,9 @@ */ abstract class CacheWarmer implements CacheWarmerInterface { + /** + * @return void + */ protected function writeCacheFile(string $file, $content) { $tmpFile = @tempnam(\dirname($file), basename($file)); diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php index af0a2d13d1a26..30132921672ca 100644 --- a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php +++ b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpKernel\CacheWarmer; +use Symfony\Component\Console\Style\SymfonyStyle; + /** * Aggregates several cache warmers into a single one. * @@ -36,17 +38,17 @@ public function __construct(iterable $warmers = [], bool $debug = false, string $this->deprecationLogsFilepath = $deprecationLogsFilepath; } - public function enableOptionalWarmers() + public function enableOptionalWarmers(): void { $this->optionalsEnabled = true; } - public function enableOnlyOptionalWarmers() + public function enableOnlyOptionalWarmers(): void { $this->onlyOptionalsEnabled = $this->optionalsEnabled = true; } - public function warmUp(string $cacheDir): array + public function warmUp(string $cacheDir, SymfonyStyle $io = null): array { if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) { $collectedLogs = []; @@ -93,7 +95,17 @@ public function warmUp(string $cacheDir): array continue; } - $preload[] = array_values((array) $warmer->warmUp($cacheDir)); + $start = microtime(true); + foreach ((array) $warmer->warmUp($cacheDir) as $item) { + if (is_dir($item) || (str_starts_with($item, \dirname($cacheDir)) && !is_file($item))) { + throw new \LogicException(sprintf('"%s::warmUp()" should return a list of files or classes but "%s" is none of them.', $warmer::class, $item)); + } + $preload[] = $item; + } + + if ($io?->isDebug()) { + $io->info(sprintf('"%s" completed in %0.2fms.', $warmer::class, 1000 * (microtime(true) - $start))); + } } } finally { if ($collectDeprecations) { @@ -110,7 +122,7 @@ public function warmUp(string $cacheDir): array } } - return array_values(array_unique(array_merge([], ...$preload))); + return array_values(array_unique($preload)); } public function isOptional(): bool diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php index 52ac242141af6..670a6966275b5 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php @@ -11,7 +11,9 @@ namespace Symfony\Component\HttpKernel\Controller; +use Psr\Container\ContainerInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\ValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; @@ -20,6 +22,8 @@ use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface; +use Symfony\Component\HttpKernel\Exception\ResolverNotFoundException; +use Symfony\Contracts\Service\ServiceProviderInterface; /** * Responsible for resolving the arguments passed to an action. @@ -30,14 +34,16 @@ final class ArgumentResolver implements ArgumentResolverInterface { private ArgumentMetadataFactoryInterface $argumentMetadataFactory; private iterable $argumentValueResolvers; + private ?ContainerInterface $namedResolvers; /** * @param iterable $argumentValueResolvers */ - public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = []) + public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = [], ContainerInterface $namedResolvers = null) { $this->argumentMetadataFactory = $argumentMetadataFactory ?? new ArgumentMetadataFactory(); $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers(); + $this->namedResolvers = $namedResolvers; } public function getArguments(Request $request, callable $controller, \ReflectionFunctionAbstract $reflector = null): array @@ -45,10 +51,37 @@ public function getArguments(Request $request, callable $controller, \Reflection $arguments = []; foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller, $reflector) as $metadata) { - foreach ($this->argumentValueResolvers as $resolver) { + $argumentValueResolvers = $this->argumentValueResolvers; + $disabledResolvers = []; + + if ($this->namedResolvers && $attributes = $metadata->getAttributesOfType(ValueResolver::class, $metadata::IS_INSTANCEOF)) { + $resolverName = null; + foreach ($attributes as $attribute) { + if ($attribute->disabled) { + $disabledResolvers[$attribute->resolver] = true; + } elseif ($resolverName) { + throw new \LogicException(sprintf('You can only pin one resolver per argument, but argument "$%s" of "%s()" has more.', $metadata->getName(), $this->getPrettyName($controller))); + } else { + $resolverName = $attribute->resolver; + } + } + + if ($resolverName) { + if (!$this->namedResolvers->has($resolverName)) { + throw new ResolverNotFoundException($resolverName, $this->namedResolvers instanceof ServiceProviderInterface ? array_keys($this->namedResolvers->getProvidedServices()) : []); + } + + $argumentValueResolvers = [$this->namedResolvers->get($resolverName)]; + } + } + + foreach ($argumentValueResolvers as $name => $resolver) { if ((!$resolver instanceof ValueResolverInterface || $resolver instanceof TraceableValueResolver) && !$resolver->supports($request, $metadata)) { continue; } + if (isset($disabledResolvers[\is_int($name) ? $resolver::class : $name])) { + continue; + } $count = 0; foreach ($resolver->resolve($request, $metadata) as $argument) { @@ -70,15 +103,7 @@ public function getArguments(Request $request, callable $controller, \Reflection } } - $representative = $controller; - - if (\is_array($representative)) { - $representative = sprintf('%s::%s()', \get_class($representative[0]), $representative[1]); - } elseif (\is_object($representative)) { - $representative = get_debug_type($representative); - } - - throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName())); + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $this->getPrettyName($controller), $metadata->getName())); } return $arguments; @@ -97,4 +122,17 @@ public static function getDefaultArgumentValueResolvers(): iterable new VariadicValueResolver(), ]; } + + private function getPrettyName($controller): string + { + if (\is_array($controller)) { + return $controller[0]::class.'::'.$controller[1]; + } + + if (\is_object($controller)) { + return get_debug_type($controller); + } + + return $controller; + } } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php index 8fd7015ad041d..0cfd42badc974 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Psr\Clock\ClockInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\MapDateTime; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; @@ -26,6 +27,11 @@ */ final class DateTimeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { + public function __construct( + private readonly ?ClockInterface $clock = null, + ) { + } + /** * @deprecated since Symfony 6.2, use resolve() instead */ @@ -45,12 +51,18 @@ public function resolve(Request $request, ArgumentMetadata $argument): array $value = $request->attributes->get($argument->getName()); $class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType(); - if ($value instanceof \DateTimeInterface) { - return [$value instanceof $class ? $value : $class::createFromInterface($value)]; + if (!$value) { + if ($argument->isNullable()) { + return [null]; + } + if (!$this->clock) { + return [new $class()]; + } + $value = $this->clock->now(); } - if ($argument->isNullable() && !$value) { - return [null]; + if ($value instanceof \DateTimeInterface) { + return [$value instanceof $class ? $value : $class::createFromInterface($value)]; } $format = null; @@ -61,7 +73,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): array } if (null !== $format) { - $date = $class::createFromFormat($format, $value); + $date = $class::createFromFormat($format, $value, $this->clock?->now()->getTimeZone()); if (($class::getLastErrors() ?: ['warning_count' => 0])['warning_count']) { $date = false; @@ -71,7 +83,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): array $value = '@'.$value; } try { - $date = new $class($value ?? 'now'); + $date = new $class($value, $this->clock?->now()->getTimeZone()); } catch (\Exception) { $date = false; } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/QueryParameterValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/QueryParameterValueResolver.php new file mode 100644 index 0000000000000..f2e4bee812d79 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/QueryParameterValueResolver.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * @author Ruud Kamphuis + * @author Nicolas Grekas + */ +final class QueryParameterValueResolver implements ValueResolverInterface +{ + public function resolve(Request $request, ArgumentMetadata $argument): array + { + if (!$attribute = $argument->getAttributesOfType(MapQueryParameter::class)[0] ?? null) { + return []; + } + + $name = $attribute->name ?? $argument->getName(); + if (!$request->query->has($name)) { + if ($argument->isNullable() || $argument->hasDefaultValue()) { + return []; + } + + throw new NotFoundHttpException(sprintf('Missing query parameter "%s".', $name)); + } + + $value = $request->query->all()[$name]; + + if (null === $attribute->filter && 'array' === $argument->getType()) { + if (!$argument->isVariadic()) { + return [(array) $value]; + } + + $filtered = array_values(array_filter((array) $value, \is_array(...))); + + if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { + throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name)); + } + + return $filtered; + } + + $options = [ + 'flags' => $attribute->flags | \FILTER_NULL_ON_FAILURE, + 'options' => $attribute->options, + ]; + + if ('array' === $argument->getType() || $argument->isVariadic()) { + $value = (array) $value; + $options['flags'] |= \FILTER_REQUIRE_ARRAY; + } else { + $options['flags'] |= \FILTER_REQUIRE_SCALAR; + } + + $filter = match ($argument->getType()) { + 'array' => \FILTER_DEFAULT, + 'string' => \FILTER_DEFAULT, + 'int' => \FILTER_VALIDATE_INT, + 'float' => \FILTER_VALIDATE_FLOAT, + 'bool' => \FILTER_VALIDATE_BOOL, + default => throw new \LogicException(sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float or bool should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $argument->getType() ?? 'mixed')) + }; + + $value = filter_var($value, $attribute->filter ?? $filter, $options); + + if (null === $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { + throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name)); + } + + if (!\is_array($value)) { + return [$value]; + } + + $filtered = array_filter($value, static fn ($v) => null !== $v); + + if ($argument->isVariadic()) { + $filtered = array_values($filtered); + } + + if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) { + throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name)); + } + + return $argument->isVariadic() ? $filtered : [$filtered]; + } +} diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php new file mode 100644 index 0000000000000..89a07c19f5ca9 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\MapQueryString; +use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; +use Symfony\Component\Serializer\Exception\PartialDenormalizationException; +use Symfony\Component\Serializer\Exception\UnsupportedFormatException; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Exception\ValidationFailedException; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Konstantin Myakshin + */ +final class RequestPayloadValueResolver implements ValueResolverInterface +{ + /** + * @see \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT + * @see DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS + */ + private const CONTEXT_DENORMALIZE = [ + 'disable_type_enforcement' => true, + 'collect_denormalization_errors' => true, + ]; + + /** + * @see DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS + */ + private const CONTEXT_DESERIALIZE = [ + 'collect_denormalization_errors' => true, + ]; + + public function __construct( + private readonly SerializerInterface&DenormalizerInterface $serializer, + private readonly ?ValidatorInterface $validator = null, + private readonly ?TranslatorInterface $translator = null, + ) { + } + + public function resolve(Request $request, ArgumentMetadata $argument): iterable + { + $payloadMappers = [ + MapQueryString::class => ['mapQueryString', Response::HTTP_NOT_FOUND], + MapRequestPayload::class => ['mapRequestPayload', Response::HTTP_UNPROCESSABLE_ENTITY], + ]; + + foreach ($payloadMappers as $mappingAttribute => [$payloadMapper, $validationFailedCode]) { + if (!$attributes = $argument->getAttributesOfType($mappingAttribute, ArgumentMetadata::IS_INSTANCEOF)) { + continue; + } + + if (!$type = $argument->getType()) { + throw new \LogicException(sprintf('Could not resolve the "$%s" controller argument: argument should be typed.', $argument->getName())); + } + + if ($this->validator) { + $violations = new ConstraintViolationList(); + try { + $payload = $this->$payloadMapper($request, $type, $attributes[0]); + } catch (PartialDenormalizationException $e) { + $trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p); + foreach ($e->getErrors() as $error) { + $parameters = ['{{ type }}' => implode('|', $error->getExpectedTypes())]; + if ($error->canUseMessageForUser()) { + $parameters['hint'] = $error->getMessage(); + } + $template = 'This value should be of type {{ type }}.'; + $message = $trans($template, $parameters, 'validators'); + $violations->add(new ConstraintViolation($message, $template, $parameters, null, $error->getPath(), null)); + } + $payload = $e->getData(); + } + + if (null !== $payload) { + $violations->addAll($this->validator->validate($payload, null, $attributes[0]->validationGroups ?? null)); + } + + if (\count($violations)) { + throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), iterator_to_array($violations))), new ValidationFailedException($payload, $violations)); + } + } else { + try { + $payload = $this->$payloadMapper($request, $type, $attributes[0]); + } catch (PartialDenormalizationException $e) { + throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), $e->getErrors())), $e); + } + } + + if (null !== $payload || $argument->isNullable()) { + return [$payload]; + } + } + + return []; + } + + private function mapQueryString(Request $request, string $type, MapQueryString $attribute): ?object + { + if (!$data = $request->query->all()) { + return null; + } + + return $this->serializer->denormalize($data, $type, null, self::CONTEXT_DENORMALIZE + $attribute->serializationContext); + } + + private function mapRequestPayload(Request $request, string $type, MapRequestPayload $attribute): ?object + { + if (null === $format = $request->getContentTypeFormat()) { + throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, 'Unsupported format.'); + } + + if ($attribute->acceptFormat && !\in_array($format, (array) $attribute->acceptFormat, true)) { + throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, sprintf('Unsupported format, expects "%s", but "%s" given.', implode('", "', (array) $attribute->acceptFormat), $format)); + } + + if ($data = $request->request->all()) { + return $this->serializer->denormalize($data, $type, null, self::CONTEXT_DENORMALIZE + $attribute->serializationContext); + } + + if ('' === $data = $request->getContent()) { + return null; + } + + if ('form' === $format) { + throw new HttpException(Response::HTTP_BAD_REQUEST, 'Request payload contains invalid "form" data.'); + } + + try { + return $this->serializer->deserialize($data, $type, $format, self::CONTEXT_DESERIALIZE + $attribute->serializationContext); + } catch (UnsupportedFormatException $e) { + throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, sprintf('Unsupported format: "%s".', $format), $e); + } catch (NotEncodableValueException $e) { + throw new HttpException(Response::HTTP_BAD_REQUEST, sprintf('Request payload contains invalid "%s" data.', $format), $e); + } + } +} diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php index edc30e1806c13..0cb4703b29a16 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php @@ -42,7 +42,7 @@ public function supports(Request $request, ArgumentMetadata $argument): bool return true; } - $method = \get_class($this->inner).'::'.__FUNCTION__; + $method = $this->inner::class.'::'.__FUNCTION__; $this->stopwatch->start($method, 'controller.argument_value_resolver'); $return = $this->inner->supports($request, $argument); @@ -54,7 +54,7 @@ public function supports(Request $request, ArgumentMetadata $argument): bool public function resolve(Request $request, ArgumentMetadata $argument): iterable { - $method = \get_class($this->inner).'::'.__FUNCTION__; + $method = $this->inner::class.'::'.__FUNCTION__; $this->stopwatch->start($method, 'controller.argument_value_resolver'); yield from $this->inner->resolve($request, $argument); diff --git a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php index 6930738a28e31..1c9254e732a7f 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php @@ -54,7 +54,7 @@ protected function instantiateController(string $class): object throw new \InvalidArgumentException(sprintf('Controller "%s" does neither exist as service nor as class.', $class), 0, $e); } - private function throwExceptionIfControllerWasRemoved(string $controller, \Throwable $previous) + private function throwExceptionIfControllerWasRemoved(string $controller, \Throwable $previous): void { if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$controller])) { throw new \InvalidArgumentException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $controller), 0, $previous); diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php index 4a08041f33775..b12ce8d35ffd6 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php @@ -197,8 +197,6 @@ private function getClassMethodsWithoutMagicMethods($classOrObject): array { $methods = get_class_methods($classOrObject); - return array_filter($methods, function (string $method) { - return 0 !== strncmp($method, '__', 2); - }); + return array_filter($methods, fn (string $method) => 0 !== strncmp($method, '__', 2)); } } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/AjaxDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/AjaxDataCollector.php index fda6a4eaaa92b..016ef2eceb2cc 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/AjaxDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/AjaxDataCollector.php @@ -21,12 +21,12 @@ */ class AjaxDataCollector extends DataCollector { - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { // all collecting is done client side } - public function reset() + public function reset(): void { // all collecting is done client side } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php index d489b023aa087..9873a9255ad64 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php @@ -29,7 +29,7 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte /** * Sets the Kernel associated with this Request. */ - public function setKernel(KernelInterface $kernel = null) + public function setKernel(KernelInterface $kernel = null): void { if (1 > \func_num_args()) { trigger_deprecation('symfony/http-kernel', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); @@ -38,7 +38,7 @@ public function setKernel(KernelInterface $kernel = null) $this->kernel = $kernel; } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { $eom = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE); $eol = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE); @@ -76,12 +76,12 @@ public function collect(Request $request, Response $response, \Throwable $except } } - public function reset() + public function reset(): void { $this->data = []; } - public function lateCollect() + public function lateCollect(): void { $this->data = $this->cloneVar($this->data); } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php index 3a3be3af494bc..698ed31397327 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php @@ -89,14 +89,14 @@ public function __wakeup() /** * @internal to prevent implementing \Serializable */ - final protected function serialize() + final protected function serialize(): void { } /** * @internal to prevent implementing \Serializable */ - final protected function unserialize(string $data) + final protected function unserialize(string $data): void { } } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php index 1cb865fd66036..8df94ccb8fa23 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php @@ -24,6 +24,8 @@ interface DataCollectorInterface extends ResetInterface { /** * Collects data for the given Request and Response. + * + * @return void */ public function collect(Request $request, Response $response, \Throwable $exception = null); diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index 48ad9acb6dd11..dc7aae68cc62e 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -68,7 +68,7 @@ public function __clone() $this->clonesIndex = ++$this->clonesCount; } - public function dump(Data $data) + public function dump(Data $data): ?string { $this->stopwatch?->start('dump'); @@ -91,9 +91,11 @@ public function dump(Data $data) ++$this->dataCount; $this->stopwatch?->stop('dump'); + + return null; } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { if (!$this->dataCount) { $this->data = []; @@ -128,7 +130,7 @@ public function collect(Request $request, Response $response, \Throwable $except } } - public function reset() + public function reset(): void { $this->stopwatch?->reset(); $this->data = []; @@ -248,7 +250,7 @@ public function __destruct() } } - private function doDump(DataDumperInterface $dumper, Data $data, string $name, string $file, int $line) + private function doDump(DataDumperInterface $dumper, Data $data, string $name, string $file, int $line): void { if ($dumper instanceof CliDumper) { $contextDumper = function ($name, $file, $line, $fmt) { diff --git a/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php index 0c122e6cff71b..8c0eefdf39b6b 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php @@ -28,76 +28,92 @@ */ class EventDataCollector extends DataCollector implements LateDataCollectorInterface { - private ?EventDispatcherInterface $dispatcher; - private ?RequestStack $requestStack; + /** @var iterable */ + private iterable $dispatchers; private ?Request $currentRequest = null; - public function __construct(EventDispatcherInterface $dispatcher = null, RequestStack $requestStack = null) - { - $this->dispatcher = $dispatcher; + /** + * @param iterable|EventDispatcherInterface|null $dispatchers + */ + public function __construct( + iterable|EventDispatcherInterface $dispatchers = null, + private ?RequestStack $requestStack = null, + private string $defaultDispatcher = 'event_dispatcher', + ) { + if ($dispatchers instanceof EventDispatcherInterface) { + $dispatchers = [$this->defaultDispatcher => $dispatchers]; + } + $this->dispatchers = $dispatchers ?? []; $this->requestStack = $requestStack; } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; - $this->data = [ - 'called_listeners' => [], - 'not_called_listeners' => [], - 'orphaned_events' => [], - ]; + $this->data = []; } - public function reset() + public function reset(): void { $this->data = []; - if ($this->dispatcher instanceof ResetInterface) { - $this->dispatcher->reset(); + foreach ($this->dispatchers as $dispatcher) { + if ($dispatcher instanceof ResetInterface) { + $dispatcher->reset(); + } } } - public function lateCollect() + public function lateCollect(): void { - if ($this->dispatcher instanceof TraceableEventDispatcher) { - $this->setCalledListeners($this->dispatcher->getCalledListeners($this->currentRequest)); - $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners($this->currentRequest)); - $this->setOrphanedEvents($this->dispatcher->getOrphanedEvents($this->currentRequest)); + foreach ($this->dispatchers as $name => $dispatcher) { + if (!$dispatcher instanceof TraceableEventDispatcher) { + continue; + } + + $this->setCalledListeners($dispatcher->getCalledListeners($this->currentRequest), $name); + $this->setNotCalledListeners($dispatcher->getNotCalledListeners($this->currentRequest), $name); + $this->setOrphanedEvents($dispatcher->getOrphanedEvents($this->currentRequest), $name); } $this->data = $this->cloneVar($this->data); } + public function getData(): array|Data + { + return $this->data; + } + /** * @see TraceableEventDispatcher */ - public function setCalledListeners(array $listeners) + public function setCalledListeners(array $listeners, string $dispatcher = null): void { - $this->data['called_listeners'] = $listeners; + $this->data[$dispatcher ?? $this->defaultDispatcher]['called_listeners'] = $listeners; } /** * @see TraceableEventDispatcher */ - public function getCalledListeners(): array|Data + public function getCalledListeners(string $dispatcher = null): array|Data { - return $this->data['called_listeners']; + return $this->data[$dispatcher ?? $this->defaultDispatcher]['called_listeners'] ?? []; } /** * @see TraceableEventDispatcher */ - public function setNotCalledListeners(array $listeners) + public function setNotCalledListeners(array $listeners, string $dispatcher = null): void { - $this->data['not_called_listeners'] = $listeners; + $this->data[$dispatcher ?? $this->defaultDispatcher]['not_called_listeners'] = $listeners; } /** * @see TraceableEventDispatcher */ - public function getNotCalledListeners(): array|Data + public function getNotCalledListeners(string $dispatcher = null): array|Data { - return $this->data['not_called_listeners']; + return $this->data[$dispatcher ?? $this->defaultDispatcher]['not_called_listeners'] ?? []; } /** @@ -105,17 +121,17 @@ public function getNotCalledListeners(): array|Data * * @see TraceableEventDispatcher */ - public function setOrphanedEvents(array $events) + public function setOrphanedEvents(array $events, string $dispatcher = null): void { - $this->data['orphaned_events'] = $events; + $this->data[$dispatcher ?? $this->defaultDispatcher]['orphaned_events'] = $events; } /** * @see TraceableEventDispatcher */ - public function getOrphanedEvents(): array|Data + public function getOrphanedEvents(string $dispatcher = null): array|Data { - return $this->data['orphaned_events']; + return $this->data[$dispatcher ?? $this->defaultDispatcher]['orphaned_events'] ?? []; } public function getName(): string diff --git a/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php index dbe24b414eb60..bcd7f238beaf2 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php @@ -22,16 +22,16 @@ */ class ExceptionDataCollector extends DataCollector { - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { if (null !== $exception) { $this->data = [ - 'exception' => FlattenException::createFromThrowable($exception), + 'exception' => FlattenException::createWithDataRepresentation($exception), ]; } } - public function reset() + public function reset(): void { $this->data = []; } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LateDataCollectorInterface.php b/src/Symfony/Component/HttpKernel/DataCollector/LateDataCollectorInterface.php index 012332de479f7..efa1a4f737f63 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/LateDataCollectorInterface.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/LateDataCollectorInterface.php @@ -20,6 +20,8 @@ interface LateDataCollectorInterface { /** * Collects data as late as possible. + * + * @return void */ public function lateCollect(); } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php index 661b84e0ade9c..d10dd365adad7 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php @@ -40,12 +40,12 @@ public function __construct(object $logger = null, string $containerPathPrefix = $this->requestStack = $requestStack; } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; } - public function reset() + public function reset(): void { if (isset($this->logger)) { $this->logger->clear(); @@ -53,7 +53,7 @@ public function reset() $this->data = []; } - public function lateCollect() + public function lateCollect(): void { if (isset($this->logger)) { $containerDeprecationLogs = $this->getContainerDeprecationLogs(); @@ -110,9 +110,7 @@ public function getProcessedLogs() } // sort logs from oldest to newest - usort($logs, static function ($logA, $logB) { - return $logA['timestamp'] <=> $logB['timestamp']; - }); + usort($logs, static fn ($logA, $logB) => $logA['timestamp'] <=> $logB['timestamp']); return $this->processedLogs = $logs; } @@ -229,7 +227,7 @@ private function getContainerCompilerLogs(string $compilerLogsFilepath = null): return $logs; } - private function sanitizeLogs(array $logs) + private function sanitizeLogs(array $logs): array { $sanitizedLogs = []; $silencedLogs = []; @@ -261,7 +259,7 @@ private function sanitizeLogs(array $logs) continue; } - $errorId = md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}", true); + $errorId = hash('xxh128', "{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}", true); if (isset($sanitizedLogs[$errorId])) { ++$sanitizedLogs[$errorId]['errorCount']; diff --git a/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php index 474b5bf3fee21..8b88943675c98 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php @@ -26,12 +26,12 @@ public function __construct() $this->reset(); } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { $this->updateMemoryUsage(); } - public function reset() + public function reset(): void { $this->data = [ 'memory' => 0, @@ -39,7 +39,7 @@ public function reset() ]; } - public function lateCollect() + public function lateCollect(): void { $this->updateMemoryUsage(); } @@ -54,7 +54,7 @@ public function getMemoryLimit(): int|float return $this->data['memory_limit']; } - public function updateMemoryUsage() + public function updateMemoryUsage(): void { $this->data['memory'] = memory_get_peak_usage(true); } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index aa505783bbd46..094683ccce4a9 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -44,7 +44,7 @@ public function __construct(RequestStack $requestStack = null) $this->requestStack = $requestStack; } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { // attributes are serialized and as they can be anything, they need to be converted to strings. $attributes = []; @@ -136,7 +136,7 @@ public function collect(Request $request, Response $response, \Throwable $except continue; } if ('request_headers' === $key || 'response_headers' === $key) { - $this->data[$key] = array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value); + $this->data[$key] = array_map(fn ($v) => isset($v[0]) && !isset($v[1]) ? $v[0] : $v, $value); } } @@ -173,12 +173,12 @@ public function collect(Request $request, Response $response, \Throwable $except } } - public function lateCollect() + public function lateCollect(): void { $this->data = $this->cloneVar($this->data); } - public function reset() + public function reset(): void { $this->data = []; $this->controllers = new \SplObjectStorage(); @@ -195,46 +195,73 @@ public function getPathInfo() return $this->data['path_info']; } + /** + * @return ParameterBag + */ public function getRequestRequest() { return new ParameterBag($this->data['request_request']->getValue()); } + /** + * @return ParameterBag + */ public function getRequestQuery() { return new ParameterBag($this->data['request_query']->getValue()); } + /** + * @return ParameterBag + */ public function getRequestFiles() { return new ParameterBag($this->data['request_files']->getValue()); } + /** + * @return ParameterBag + */ public function getRequestHeaders() { return new ParameterBag($this->data['request_headers']->getValue()); } + /** + * @return ParameterBag + */ public function getRequestServer(bool $raw = false) { return new ParameterBag($this->data['request_server']->getValue($raw)); } + /** + * @return ParameterBag + */ public function getRequestCookies(bool $raw = false) { return new ParameterBag($this->data['request_cookies']->getValue($raw)); } + /** + * @return ParameterBag + */ public function getRequestAttributes() { return new ParameterBag($this->data['request_attributes']->getValue()); } + /** + * @return ParameterBag + */ public function getResponseHeaders() { return new ParameterBag($this->data['response_headers']->getValue()); } + /** + * @return ParameterBag + */ public function getResponseCookies() { return new ParameterBag($this->data['response_cookies']->getValue()); @@ -270,11 +297,17 @@ public function getContent() return $this->data['content']; } + /** + * @return bool + */ public function isJsonRequest() { return 1 === preg_match('{^application/(?:\w+\++)*json$}i', $this->data['request_headers']['content-type']); } + /** + * @return string|null + */ public function getPrettyJson() { $decoded = json_decode($this->getContent()); @@ -307,6 +340,9 @@ public function getLocale() return $this->data['locale']; } + /** + * @return ParameterBag + */ public function getDotenvVars() { return new ParameterBag($this->data['dotenv_vars']->getValue()); @@ -364,12 +400,12 @@ public function getForwardToken() return $this->data['forward_token'] ?? null; } - public function onKernelController(ControllerEvent $event) + public function onKernelController(ControllerEvent $event): void { $this->controllers[$event->getRequest()] = $event->getController(); } - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php index 3c9f2142f493d..444138da70346 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php @@ -34,7 +34,7 @@ public function __construct() /** * @final */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { if ($response instanceof RedirectResponse) { $this->data['redirect'] = true; @@ -48,6 +48,9 @@ public function collect(Request $request, Response $response, \Throwable $except unset($this->controllers[$request]); } + /** + * @return void + */ public function reset() { $this->controllers = new \SplObjectStorage(); @@ -59,6 +62,9 @@ public function reset() ]; } + /** + * @return string + */ protected function guessRoute(Request $request, string|object|array $controller) { return 'n/a'; @@ -66,6 +72,8 @@ protected function guessRoute(Request $request, string|object|array $controller) /** * Remembers the controller associated to each request. + * + * @return void */ public function onKernelController(ControllerEvent $event) { diff --git a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php index 71bf255d01af0..a8b7ead94073f 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php @@ -34,7 +34,7 @@ public function __construct(KernelInterface $kernel = null, Stopwatch $stopwatch $this->data = ['events' => [], 'stopwatch_installed' => false, 'start_time' => 0]; } - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null): void { if (null !== $this->kernel) { $startTime = $this->kernel->getStartTime(); @@ -50,14 +50,14 @@ public function collect(Request $request, Response $response, \Throwable $except ]; } - public function reset() + public function reset(): void { $this->data = ['events' => [], 'stopwatch_installed' => false, 'start_time' => 0]; $this->stopwatch?->reset(); } - public function lateCollect() + public function lateCollect(): void { if (null !== $this->stopwatch && isset($this->data['token'])) { $this->setEvents($this->stopwatch->getSectionEvents($this->data['token'])); @@ -68,7 +68,7 @@ public function lateCollect() /** * @param StopwatchEvent[] $events The request events */ - public function setEvents(array $events) + public function setEvents(array $events): void { foreach ($events as $event) { $event->ensureStopped(); diff --git a/src/Symfony/Component/HttpKernel/Debug/ErrorHandlerConfigurator.php b/src/Symfony/Component/HttpKernel/Debug/ErrorHandlerConfigurator.php new file mode 100644 index 0000000000000..49f188c22d9d0 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Debug/ErrorHandlerConfigurator.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Psr\Log\LoggerInterface; +use Symfony\Component\ErrorHandler\ErrorHandler; + +/** + * Configures the error handler. + * + * @final + * + * @internal + */ +class ErrorHandlerConfigurator +{ + private ?LoggerInterface $logger; + private ?LoggerInterface $deprecationLogger; + private array|int|null $levels; + private ?int $throwAt; + private bool $scream; + private bool $scope; + + /** + * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged + * @param bool $scope Enables/disables scoping mode + */ + public function __construct(LoggerInterface $logger = null, array|int|null $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, bool $scope = true, LoggerInterface $deprecationLogger = null) + { + $this->logger = $logger; + $this->levels = $levels ?? \E_ALL; + $this->throwAt = \is_int($throwAt) ? $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null)); + $this->scream = $scream; + $this->scope = $scope; + $this->deprecationLogger = $deprecationLogger; + } + + /** + * Configures the error handler. + */ + public function configure(ErrorHandler $handler): void + { + if ($this->logger || $this->deprecationLogger) { + $this->setDefaultLoggers($handler); + if (\is_array($this->levels)) { + $levels = 0; + foreach ($this->levels as $type => $log) { + $levels |= $type; + } + } else { + $levels = $this->levels; + } + + if ($this->scream) { + $handler->screamAt($levels); + } + if ($this->scope) { + $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED); + } else { + $handler->scopeAt(0, true); + } + $this->logger = $this->deprecationLogger = $this->levels = null; + } + if (null !== $this->throwAt) { + $handler->throwAt($this->throwAt, true); + } + } + + private function setDefaultLoggers(ErrorHandler $handler): void + { + if (\is_array($this->levels)) { + $levelsDeprecatedOnly = []; + $levelsWithoutDeprecated = []; + foreach ($this->levels as $type => $log) { + if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) { + $levelsDeprecatedOnly[$type] = $log; + } else { + $levelsWithoutDeprecated[$type] = $log; + } + } + } else { + $levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED); + $levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED; + } + + $defaultLoggerLevels = $this->levels; + if ($this->deprecationLogger && $levelsDeprecatedOnly) { + $handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly); + $defaultLoggerLevels = $levelsWithoutDeprecated; + } + + if ($this->logger && $defaultLoggerLevels) { + $handler->setDefaultLogger($this->logger, $defaultLoggerLevels); + } + } +} diff --git a/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php b/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php index 4eaeeb1513713..fcb100859f64d 100644 --- a/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php +++ b/src/Symfony/Component/HttpKernel/Debug/FileLinkFormatter.php @@ -37,8 +37,9 @@ public function __construct(string|array $fileLinkFormat = null, RequestStack $r { $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); + if (!\is_array($f = $fileLinkFormat)) { + $f = (ErrorRendererInterface::IDE_LINK_FORMATS[$f] ?? $f) ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'; + $i = strpos($f, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); $fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE); } @@ -48,7 +49,10 @@ public function __construct(string|array $fileLinkFormat = null, RequestStack $r $this->urlFormat = $urlFormat; } - public function format(string $file, int $line) + /** + * @return string|false + */ + public function format(string $file, int $line): string|bool { if ($fmt = $this->getFileLinkFormat()) { for ($i = 1; isset($fmt[$i]); ++$i) { diff --git a/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php index dff3bc9474ab2..4f6c34bc745bf 100644 --- a/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php +++ b/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php @@ -23,6 +23,9 @@ */ class TraceableEventDispatcher extends BaseTraceableEventDispatcher { + /** + * @return void + */ protected function beforeDispatch(string $eventName, object $event) { switch ($eventName) { @@ -55,6 +58,9 @@ protected function beforeDispatch(string $eventName, object $event) } } + /** + * @return void + */ protected function afterDispatch(string $eventName, object $event) { switch ($eventName) { diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php index 9b691d26ef9d9..1924b1ddb0af6 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php @@ -31,6 +31,9 @@ public function __construct(Kernel $kernel) $this->kernel = $kernel; } + /** + * @return void + */ public function process(ContainerBuilder $container) { $annotatedClasses = []; diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php index 8401dd4e8c03c..12d468cf04865 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php @@ -27,13 +27,15 @@ */ abstract class ConfigurableExtension extends Extension { - final public function load(array $configs, ContainerBuilder $container) + final public function load(array $configs, ContainerBuilder $container): void { $this->loadInternal($this->processConfiguration($this->getConfiguration($configs, $container), $configs), $container); } /** * Configures the passed container according to the merged configuration. + * + * @return void */ abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container); } diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php index 281a7782953a0..dff3e248aee1a 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php @@ -12,6 +12,8 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -28,16 +30,33 @@ class ControllerArgumentValueResolverPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('argument_resolver')) { return; } - $resolvers = $this->findAndSortTaggedServices('controller.argument_value_resolver', $container); + $definitions = $container->getDefinitions(); + $namedResolvers = $this->findAndSortTaggedServices(new TaggedIteratorArgument('controller.targeted_value_resolver', 'name', needsIndexes: true), $container); + $resolvers = $this->findAndSortTaggedServices(new TaggedIteratorArgument('controller.argument_value_resolver', 'name', needsIndexes: true), $container); + + foreach ($resolvers as $name => $resolverReference) { + $id = (string) $resolverReference; + + if ($definitions[$id]->hasTag('controller.targeted_value_resolver')) { + unset($resolvers[$name]); + } else { + $namedResolvers[$name] ??= clone $resolverReference; + } + } + + $resolvers = array_values($resolvers); if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class) && $container->has('debug.stopwatch')) { - foreach ($resolvers as $resolverReference) { + foreach ($resolvers + $namedResolvers as $resolverReference) { $id = (string) $resolverReference; $container->register("debug.$id", TraceableValueResolver::class) ->setDecoratedService($id) @@ -48,6 +67,7 @@ public function process(ContainerBuilder $container) $container ->getDefinition('argument_resolver') ->replaceArgument(1, new IteratorArgument($resolvers)) + ->setArgument(2, new ServiceLocatorArgument($namedResolvers)) ; } } diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php b/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php index c8d546e9d9531..d72efa17242f6 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php @@ -34,6 +34,8 @@ public function getAnnotatedClassesToCompile(): array * Adds annotated classes to the class cache. * * @param array $annotatedClasses An array of class patterns + * + * @return void */ public function addAnnotatedClassesToCompile(array $annotatedClasses) { diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php index ced62ae38078a..f41d58b81b6ae 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/FragmentRendererPass.php @@ -25,6 +25,9 @@ */ class FragmentRendererPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('fragment.handler')) { diff --git a/src/Symfony/Component/HttpKernel/Depende 97AE ncyInjection/LoggerPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/LoggerPass.php index d780645f1efbf..2b6cb00793bf6 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/LoggerPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/LoggerPass.php @@ -25,6 +25,9 @@ */ class LoggerPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { $container->setAlias(LoggerInterface::class, 'logger') diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php index c0bc6eccf261a..cec23e19703f0 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php @@ -31,6 +31,9 @@ public function __construct(array $extensions) $this->extensions = $extensions; } + /** + * @return void + */ public function process(ContainerBuilder $container) { foreach ($this->extensions as $extension) { diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index c31fb35d71c9b..d0e05340d8c6a 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -33,6 +33,9 @@ */ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('argument_resolver.service') && !$container->hasDefinition('argument_resolver.not_tagged_controller')) { @@ -143,7 +146,7 @@ public function process(ContainerBuilder $container) $args[$p->name] = $bindingValue; continue; - } elseif (!$autowire || (!($autowireAttributes ??= $p->getAttributes(Autowire::class)) && (!$type || '\\' !== $target[0]))) { + } elseif (!$autowire || (!($autowireAttributes ??= $p->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF)) && (!$type || '\\' !== $target[0]))) { continue; } elseif (is_subclass_of($type, \UnitEnum::class)) { // do not attempt to register enum typed arguments if not already present in bindings diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php index 2141a510d62c0..2a01365bd330c 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php @@ -23,6 +23,9 @@ */ class RegisterLocaleAwareServicesPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('locale_aware_listener')) { diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php index cbc92355d6873..7a21fe0e5943e 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -21,6 +21,9 @@ */ class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { $controllerLocator = $container->findDefinition('argument_resolver.controller_locator'); diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php index 8418b4da4a68e..da9f8d6320914 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php @@ -23,6 +23,9 @@ */ class ResettableServicePass implements CompilerPassInterface { + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->has('services_resetter')) { diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php index e7be3b88e7a34..15e40b3ce9b23 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php @@ -36,7 +36,7 @@ public function __construct(\Traversable $resettableServices, array $resetMethod $this->resetMethods = $resetMethods; } - public function reset() + public function reset(): void { foreach ($this->resettableServices as $id => $service) { foreach ((array) $this->resetMethods[$id] as $resetMethod) { diff --git a/src/Symfony/Component/HttpKernel/Event/ControllerArgumentsEvent.php b/src/Symfony/Component/HttpKernel/Event/ControllerArgumentsEvent.php index 0af39671e69f6..64063abf25dde 100644 --- a/src/Symfony/Component/HttpKernel/Event/ControllerArgumentsEvent.php +++ b/src/Symfony/Component/HttpKernel/Event/ControllerArgumentsEvent.php @@ -63,7 +63,7 @@ public function getArguments(): array return $this->arguments; } - public function setArguments(array $arguments) + public function setArguments(array $arguments): void { $this->arguments = $arguments; unset($this->namedArguments); diff --git a/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php b/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php index b012263b93a61..d07d886db0e5d 100644 --- a/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php +++ b/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php @@ -69,7 +69,7 @@ public function setController(callable $controller, array $attributes = null): v if (\is_array($controller) && method_exists(...$controller)) { $this->controllerReflector = new \ReflectionMethod(...$controller); - } elseif (\is_string($controller) && false !== $i = strpos($controller, '::')) { + } elseif (\is_string($controller) && str_contains($controller, '::')) { $this->controllerReflector = new \ReflectionMethod($controller); } else { $this->controllerReflector = new \ReflectionFunction($controller(...)); diff --git a/src/Symfony/Component/HttpKernel/Event/RequestEvent.php b/src/Symfony/Component/HttpKernel/Event/RequestEvent.php index c7cfcc1e1311e..b81a79b780be9 100644 --- a/src/Symfony/Component/HttpKernel/Event/RequestEvent.php +++ b/src/Symfony/Component/HttpKernel/Event/RequestEvent.php @@ -36,6 +36,8 @@ public function getResponse(): ?Response /** * Sets a response and stops event propagation. + * + * @return void */ public function setResponse(Response $response) { diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php index c85e839230670..ec27eaec122e5 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php @@ -57,7 +57,7 @@ public function __construct(ContainerInterface $container = null, bool $debug = $this->sessionOptions = $sessionOptions; } - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { if (!$event->isMainRequest()) { return; @@ -65,9 +65,10 @@ public function onKernelRequest(RequestEvent $event) $request = $event->getRequest(); if (!$request->hasSession()) { - // This variable prevents calling `$this->getSession()` twice in case the Request (and the below factory) is cloned - $sess = null; - $request->setSessionFactory(function () use (&$sess, $request) { + $request->setSessionFactory(function () use ($request) { + // Prevent calling `$this->getSession()` twice in case the Request (and the below factory) is cloned + static $sess; + if (!$sess) { $sess = $this->getSession(); $request->setSession($sess); @@ -89,7 +90,7 @@ public function onKernelRequest(RequestEvent $event) } } - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest() || (!$this->container->has('initialized_session') && !$event->getRequest()->hasSession())) { return; diff --git a/src/Symfony/Component/HttpKernel/EventListener/AddRequestFormatsListener.php b/src/Symfony/Component/HttpKernel/EventListener/AddRequestFormatsListener.php index c3fc3fb657229..d4ef3fe498b5b 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AddRequestFormatsListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AddRequestFormatsListener.php @@ -34,7 +34,7 @@ public function __construct(array $formats) /** * Adds request formats. */ - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); foreach ($this->formats as $format => $mimeTypes) { diff --git a/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php b/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php index 2230b8f6998bf..723e758cd0c89 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php @@ -46,6 +46,8 @@ public function __construct( /** * Handles HTTP validation headers. + * + * @return void */ public function onKernelControllerArguments(ControllerArgumentsEvent $event) { @@ -90,6 +92,8 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event) /** * Modifies the response to apply HTTP cache headers when needed. + * + * @return void */ public function onKernelResponse(ResponseEvent $event) { diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index 4f9ceb73e3621..e3e8924928fbb 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -11,7 +11,6 @@ namespace Symfony\Component\HttpKernel\EventListener; -use Psr\Log\LoggerInterface; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleEvent; use Symfony\Component\Console\Output\ConsoleOutputInterface; @@ -21,7 +20,7 @@ use Symfony\Component\HttpKernel\KernelEvents; /** - * Configures errors and exceptions handlers. + * Sets an exception handler. * * @author Nicolas Grekas * @@ -33,41 +32,25 @@ class DebugHandlersListener implements EventSubscriberInterface { private string|object|null $earlyHandler; private ?\Closure $exceptionHandler; - private ?LoggerInterface $logger; - private ?LoggerInterface $deprecationLogger; - private array|int|null $levels; - private ?int $throwAt; - private bool $scream; - private bool $scope; private bool $firstCall = true; private bool $hasTerminatedWithException = false; /** - * @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception - * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants - * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value - * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged - * @param bool $scope Enables/disables scoping mode + * @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception */ - public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, array|int|null $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, bool $scope = true, LoggerInterface $deprecationLogger = null) + public function __construct(callable $exceptionHandler = null) { $handler = set_exception_handler('is_int'); $this->earlyHandler = \is_array($handler) ? $handler[0] : null; restore_exception_handler(); $this->exceptionHandler = null === $exceptionHandler ? null : $exceptionHandler(...); - $this->logger = $logger; - $this->levels = $levels ?? \E_ALL; - $this->throwAt = \is_int($throwAt) ? $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null)); - $this->scream = $scream; - $this->scope = $scope; - $this->deprecationLogger = $deprecationLogger; } /** * Configures the error handler. */ - public function configure(object $event = null) + public function configure(object $event = null): void { if ($event instanceof ConsoleEvent && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { return; @@ -77,40 +60,6 @@ public function configure(object $event = null) } $this->firstCall = $this->hasTerminatedWithException = false; - $handler = set_exception_handler('is_int'); - $handler = \is_array($handler) ? $handler[0] : null; - restore_exception_handler(); - - if (!$handler instanceof ErrorHandler) { - $handler = $this->earlyHandler; - } - - if ($handler instanceof ErrorHandler) { - if ($this->logger || $this->deprecationLogger) { - $this->setDefaultLoggers($handler); - if (\is_array($this->levels)) { - $levels = 0; - foreach ($this->levels as $type => $log) { - $levels |= $type; - } - } else { - $levels = $this->levels; - } - - if ($this->scream) { - $handler->screamAt($levels); - } - if ($this->scope) { - $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED); - } else { - $handler->scopeAt(0, true); - } - $this->logger = $this->deprecationLogger = $this->levels = null; - } - if (null !== $this->throwAt) { - $handler->throwAt($this->throwAt, true); - } - } if (!$this->exceptionHandler) { if ($event instanceof KernelEvent) { if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) { @@ -136,6 +85,14 @@ public function configure(object $event = null) } } if ($this->exceptionHandler) { + $handler = set_exception_handler('is_int'); + $handler = \is_array($handler) ? $handler[0] : null; + restore_exception_handler(); + + if (!$handler instanceof ErrorHandler) { + $handler = $this->earlyHandler; + } + if ($handler instanceof ErrorHandler) { $handler->setExceptionHandler($this->exceptionHandler); } @@ -143,34 +100,6 @@ public function configure(object $event = null) } } - private function setDefaultLoggers(ErrorHandler $handler): void - { - if (\is_array($this->levels)) { - $levelsDeprecatedOnly = []; - $levelsWithoutDeprecated = []; - foreach ($this->levels as $type => $log) { - if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) { - $levelsDeprecatedOnly[$type] = $log; - } else { - $levelsWithoutDeprecated[$type] = $log; - } - } - } else { - $levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED); - $levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED; - } - - $defaultLoggerLevels = $this->levels; - if ($this->deprecationLogger && $levelsDeprecatedOnly) { - $handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly); - $defaultLoggerLevels = $levelsWithoutDeprecated; - } - - if ($this->logger && $defaultLoggerLevels) { - $handler->setDefaultLogger($this->logger, $defaultLoggerLevels); - } - } - public static function getSubscribedEvents(): array { $events = [KernelEvents::REQUEST => ['configure', 2048]]; diff --git a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php index 151f96ea7e0d7..4a4090915770e 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php @@ -36,6 +36,9 @@ public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper $this->connection = $connection; } + /** + * @return void + */ public function configure() { $cloner = $this->cloner; diff --git a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php index dd6105052028a..cc6936cfda963 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php @@ -12,9 +12,12 @@ namespace Symfony\Component\HttpKernel\EventListener; use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Attribute\WithHttpStatus; +use Symfony\Component\HttpKernel\Attribute\WithLogLevel; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; @@ -48,17 +51,13 @@ public function __construct(string|object|array|null $controller, LoggerInterfac $this->exceptionsMapping = $exceptionsMapping; } + /** + * @return void + */ public function logKernelException(ExceptionEvent $event) { $throwable = $event->getThrowable(); - $logLevel = null; - - foreach ($this->exceptionsMapping as $class => $config) { - if ($throwable instanceof $class && $config['log_level']) { - $logLevel = $config['log_level']; - break; - } - } + $logLevel = $this->resolveLogLevel($throwable); foreach ($this->exceptionsMapping as $class => $config) { if (!$throwable instanceof $class || !$config['status_code']) { @@ -72,11 +71,30 @@ public function logKernelException(ExceptionEvent $event) break; } + // There's no specific status code defined in the configuration for this exception + if (!$throwable instanceof HttpExceptionInterface) { + $class = new \ReflectionClass($throwable); + + do { + if ($attributes = $class->getAttributes(WithHttpStatus::class, \ReflectionAttribute::IS_INSTANCEOF)) { + /** @var WithHttpStatus $instance */ + $instance = $attributes[0]->newInstance(); + + $throwable = new HttpException($instance->statusCode, $throwable->getMessage(), $throwable, $instance->headers); + $event->setThrowable($throwable); + break; + } + } while ($class = $class->getParentClass()); + } + $e = FlattenException::createFromThrowable($throwable); $this->logException($throwable, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), $e->getFile(), $e->getLine()), $logLevel); } + /** + * @return void + */ public function onKernelException(ExceptionEvent $event) { if (null === $this->controller) { @@ -120,6 +138,9 @@ public function removeCspHeader(ResponseEvent $event): void } } + /** + * @return void + */ public function onControllerArguments(ControllerArgumentsEvent $event) { $e = $event->getRequest()->attributes->get('exception'); @@ -155,15 +176,42 @@ public static function getSubscribedEvents(): array */ protected function logException(\Throwable $exception, string $message, string $logLevel = null): void { - if (null !== $this->logger) { - if (null !== $logLevel) { - $this->logger->log($logLevel, $message, ['exception' => $exception]); - } elseif (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) { - $this->logger->critical($message, ['exception' => $exception]); - } else { - $this->logger->error($message, ['exception' => $exception]); + if (null === $this->logger) { + return; + } + + $logLevel ??= $this->resolveLogLevel($exception); + + $this->logger->log($logLevel, $message, ['exception' => $exception]); + } + + /** + * Resolves the level to be used when logging the exception. + */ + private function resolveLogLevel(\Throwable $throwable): string + { + foreach ($this->exceptionsMapping as $class => $config) { + if ($throwable instanceof $class && $config['log_level']) { + return $config['log_level']; + } + } + + $class = new \ReflectionClass($throwable); + + do { + if ($attributes = $class->getAttributes(WithLogLevel::class)) { + /** @var WithLogLevel $instance */ + $instance = $attributes[0]->newInstance(); + + return $instance->level; } + } while ($class = $class->getParentClass()); + + if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() >= 500) { + return LogLevel::CRITICAL; } + + return LogLevel::ERROR; } /** diff --git a/src/Symfony/Component/HttpKernel/EventListener/FragmentListener.php b/src/Symfony/Component/HttpKernel/EventListener/FragmentListener.php index 5c2fb09623653..6392d699ff108 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/FragmentListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/FragmentListener.php @@ -50,7 +50,7 @@ public function __construct(UriSigner $signer, string $fragmentPath = '/_fragmen * * @throws AccessDeniedHttpException if the request does not come from a trusted IP */ - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); @@ -75,7 +75,7 @@ public function onKernelRequest(RequestEvent $event) $request->query->remove('_path'); } - protected function validateRequest(Request $request) + protected function validateRequest(Request $request): void { // is the Request safe? if (!$request->isMethodSafe()) { diff --git a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php index 2502fa4be9d23..4516048be7f4c 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php @@ -44,12 +44,12 @@ public function __construct(RequestStack $requestStack, string $defaultLocale = $this->enabledLocales = $enabledLocales; } - public function setDefaultLocale(KernelEvent $event) + public function setDefaultLocale(KernelEvent $event): void { $event->getRequest()->setDefaultLocale($this->defaultLocale); } - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); @@ -57,14 +57,14 @@ public function onKernelRequest(RequestEvent $event) $this->setRouterContext($request); } - public function onKernelFinishRequest(FinishRequestEvent $event) + public function onKernelFinishRequest(FinishRequestEvent $event): void { if (null !== $parentRequest = $this->requestStack->getParentRequest()) { $this->setRouterContext($parentRequest); } } - private function setLocale(Request $request) + private function setLocale(Request $request): void { if ($locale = $request->attributes->get('_locale')) { $request->setLocale($locale); @@ -76,7 +76,7 @@ private function setLocale(Request $request) } } - private function setRouterContext(Request $request) + private function setRouterContext(Request $request): void { $this->router?->getContext()->setParameter('_locale', $request->getLocale()); } diff --git a/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php b/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php index d63c0493d74c4..716d939fd023a 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php @@ -63,7 +63,7 @@ public function __construct(Profiler $profiler, RequestStack $requestStack, Requ /** * Handles the onKernelException event. */ - public function onKernelException(ExceptionEvent $event) + public function onKernelException(ExceptionEvent $event): void { if ($this->onlyMainRequests && !$event->isMainRequest()) { return; @@ -75,7 +75,7 @@ public function onKernelException(ExceptionEvent $event) /** * Handles the onKernelResponse event. */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if ($this->onlyMainRequests && !$event->isMainRequest()) { return; @@ -119,7 +119,7 @@ public function onKernelResponse(ResponseEvent $event) $this->parents[$request] = $this->requestStack->getParentRequest(); } - public function onKernelTerminate(TerminateEvent $event) + public function onKernelTerminate(TerminateEvent $event): void { // attach children to parents foreach ($this->profiles as $request) { diff --git a/src/Symfony/Component/HttpKernel/EventListener/ResponseListener.php b/src/Symfony/Component/HttpKernel/EventListener/ResponseListener.php index 435fd31d1bb77..5825e16e44213 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ResponseListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ResponseListener.php @@ -36,7 +36,7 @@ public function __construct(string $charset, bool $addContentLanguageHeader = fa /** * Filters the Response. */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php b/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php index 72be3e6e78d5e..bb393c7799fcd 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php @@ -67,7 +67,7 @@ public function __construct(UrlMatcherInterface|RequestMatcherInterface $matcher $this->debug = $debug; } - private function setCurrentRequest(?Request $request) + private function setCurrentRequest(?Request $request): void { if (null !== $request) { try { @@ -82,12 +82,12 @@ private function setCurrentRequest(?Request $request) * After a sub-request is done, we need to reset the routing context to the parent request so that the URL generator * operates on the correct context again. */ - public function onKernelFinishRequest() + public function onKernelFinishRequest(): void { $this->setCurrentRequest($this->requestStack->getParentRequest()); } - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { $request = $event->getRequest(); @@ -132,7 +132,7 @@ public function onKernelRequest(RequestEvent $event) } } - public function onKernelException(ExceptionEvent $event) + public function onKernelException(ExceptionEvent $event): void { if (!$this->debug || !($e = $event->getThrowable()) instanceof NotFoundHttpException) { return; diff --git a/src/Symfony/Component/HttpKernel/EventListener/StreamedResponseListener.php b/src/Symfony/Component/HttpKernel/EventListener/StreamedResponseListener.php index c0b74fdf0ed61..312d5ee23b68e 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/StreamedResponseListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/StreamedResponseListener.php @@ -33,7 +33,7 @@ class StreamedResponseListener implements EventSubscriberInterface /** * Filters the Response. */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php b/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php index 3d1be0f2e1097..17bdf2b392789 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php @@ -36,7 +36,7 @@ public function __construct(SurrogateInterface $surrogate = null) /** * Filters the Response. */ - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/src/Symfony/Component/HttpKernel/EventListener/ValidateRequestListener.php b/src/Symfony/Component/HttpKernel/EventListener/ValidateRequestListener.php index e2501c57773a5..187633d5d4764 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ValidateRequestListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ValidateRequestListener.php @@ -27,7 +27,7 @@ class ValidateRequestListener implements EventSubscriberInterface /** * Performs the validation. */ - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/src/Symfony/Component/HttpKernel/Exception/HttpException.php b/src/Symfony/Component/HttpKernel/Exception/HttpException.php index 418271065aeae..e12abce0042a1 100644 --- a/src/Symfony/Component/HttpKernel/Exception/HttpException.php +++ b/src/Symfony/Component/HttpKernel/Exception/HttpException.php @@ -39,6 +39,9 @@ public function getHeaders(): array return $this->headers; } + /** + * @return void + */ public function setHeaders(array $headers) { $this->headers = $headers; diff --git a/src/Symfony/Component/HttpKernel/Exception/ResolverNotFoundException.php b/src/Symfony/Component/HttpKernel/Exception/ResolverNotFoundException.php new file mode 100644 index 0000000000000..6d9fb8a01f46c --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Exception/ResolverNotFoundException.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\HttpKernel\Exception; + +class ResolverNotFoundException extends \RuntimeException +{ + /** + * @param string[] $alternatives + */ + public function __construct(string $name, array $alternatives = []) + { + $msg = sprintf('You have requested a non-existent resolver "%s".', $name); + if ($alternatives) { + if (1 === \count($alternatives)) { + $msg .= ' Did you mean this: "'; + } else { + $msg .= ' Did you mean one of these: "'; + } + $msg .= implode('", "', $alternatives).'"?'; + } + + parent::__construct($msg); + } +} diff --git a/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php b/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php index dfc03db42d74d..62b21e6d4e083 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php +++ b/src/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php @@ -48,6 +48,8 @@ public function __construct(RequestStack $requestStack, array $renderers = [], b /** * Adds a renderer. + * + * @return void */ public function addRenderer(FragmentRendererInterface $renderer) { diff --git a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php index 0e53768ee9a13..3650d1700d662 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php @@ -103,6 +103,9 @@ public function render(string|ControllerReference $uri, Request $request, array } } + /** + * @return Request + */ protected function createSubRequest(string $uri, Request $request) { $cookies = $request->cookies->all(); diff --git a/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php index 416b670417b90..47027233a7220 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php @@ -31,6 +31,8 @@ abstract class RoutableFragmentRenderer implements FragmentRendererInterface * Sets the fragment path that triggers the fragment listener. * * @see FragmentListener + * + * @return void */ public function setFragmentPath(string $path) { diff --git a/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php b/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php index ca01a9240b373..ce34979bbd2a0 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php @@ -24,6 +24,10 @@ abstract class AbstractSurrogate implements SurrogateInterface { protected $contentTypes; + + /** + * @deprecated since Symfony 6.3 + */ protected $phpEscapeMap = [ ['', '', '', ''], @@ -55,6 +59,9 @@ public function hasSurrogateCapability(Request $request): bool return str_contains($value, sprintf('%s/1.0', strtoupper($this->getName()))); } + /** + * @return void + */ public function addSurrogateCapability(Request $request) { $current = $request->headers->get('Surrogate-Capability'); @@ -101,6 +108,8 @@ public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreE /** * Remove the Surrogate from the Surrogate-Control header. + * + * @return void */ protected function removeFromControl(Response $response) { diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php index 9e7c3f898ab83..d1761ccaaeb5a 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php @@ -32,6 +32,9 @@ public function getName(): string return 'esi'; } + /** + * @return void + */ public function addSurrogateControl(Response $response) { if (str_contains($response->getContent(), '.*?#s', '', $content); $content = preg_replace('#]+>#s', '', $content); + static $cookie; + $cookie = hash('xxh128', $cookie ??= random_bytes(16), true); + $boundary = base64_encode($cookie); $chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); - $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); $i = 1; while (isset($chunks[$i])) { @@ -86,16 +91,10 @@ public function process(Request $request, Response $response): Response throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); } - $chunks[$i] = sprintf('surrogate->handle($this, %s, %s, %s) ?>'."\n", - var_export($options['src'], true), - var_export($options['alt'] ?? '', true), - isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false' - 741A ); - ++$i; - $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); - ++$i; + $chunks[$i] = $boundary.$options['src']."\n".($options['alt'] ?? '')."\n".('continue' === ($options['onerror'] ?? ''))."\n"; + $i += 2; } - $content = implode('', $chunks); + $content = $boundary.implode('', $chunks).$boundary; $response->setContent($content); $response->headers->set('X-Body-Eval', 'ESI'); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index f77a99e0ffa83..374fba8af5441 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -60,6 +60,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * on responses that don't explicitly state whether the response is * public or private via a Cache-Control directive. (default: Authorization and Cookie) * + * * skip_response_headers Set of response headers that are never cached even if a response is cacheable (public). + * (default: Set-Cookie) + * * * allow_reload Specifies whether the client can force a cache reload by including a * Cache-Control "no-cache" directive in the request. Set it to ``true`` * for compliance with RFC 2616. (default: false) @@ -97,6 +100,7 @@ public function __construct(HttpKernelInterface $kernel, StoreInterface $store, 'debug' => false, 'default_ttl' => 0, 'private_headers' => ['Authorization', 'Cookie'], + 'skip_response_headers' => ['Set-Cookie'], 'allow_reload' => false, 'allow_revalidate' => false, 'stale_while_revalidate' => 2, @@ -127,7 +131,7 @@ public function getTraces(): array return $this->traces; } - private function addTraces(Response $response) + private function addTraces(Response $response): void { $traceString = null; @@ -236,6 +240,9 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R return $response; } + /** + * @return void + */ public function terminate(Request $request, Response $response) { // Do not call any listeners in case of a cache hit. @@ -586,13 +593,24 @@ protected function lock(Request $request, Response $entry): bool /** * Writes the Response to the cache. * + * @return void + * * @throws \Exception */ protected function store(Request $request, Response $response) { try { - $this->store->write($request, $response); + $restoreHeaders = []; + foreach ($this->options['skip_response_headers'] as $header) { + if (!$response->headers->has($header)) { + continue; + } + + $restoreHeaders[$header] = $response->headers->all($header); + $response->headers->remove($header); + } + $this->store->write($request, $response); $this->record($request, 'store'); $response->headers->set('Age', $response->getAge()); @@ -602,6 +620,10 @@ protected function store(Request $request, Response $response) if ($this->options['debug']) { throw $e; } + } finally { + foreach ($restoreHeaders as $header => $values) { + $response->headers->set($header, $values); + } } // now that the response is cached, release the lock @@ -611,7 +633,7 @@ protected function store(Request $request, Response $response) /** * Restores the Response body. */ - private function restoreResponseBody(Request $request, Response $response) + private function restoreResponseBody(Request $request, Response $response): void { if ($response->headers->has('X-Body-Eval')) { ob_start(); @@ -619,7 +641,21 @@ private function restoreResponseBody(Request $request, Response $response) if ($response->headers->has('X-Body-File')) { include $response->headers->get('X-Body-File'); } else { - eval('; ?>'.$response->getContent().'getContent(); + + if (substr($content, -24) === $boundary = substr($content, 0, 24)) { + $j = strpos($content, $boundary, 24); + echo substr($content, 24, $j - 24); + $i = $j + 24; + + while (false !== $j = strpos($content, $boundary, $i)) { + [$uri, $alt, $ignoreErrors, $part] = explode("\n", substr($content, $i, $j - $i), 4); + $i = $j + 24; + + echo $this->surrogate->handle($this, $uri, $alt, $ignoreErrors); + echo $part; + } + } } $response->setContent(ob_get_clean()); @@ -640,6 +676,9 @@ private function restoreResponseBody(Request $request, Response $response) $response->headers->remove('X-Body-File'); } + /** + * @return void + */ protected function processResponseBody(Request $request, Response $response) { if ($this->surrogate?->needsParsing($response)) { @@ -671,7 +710,7 @@ private function isPrivateRequest(Request $request): bool /** * Records that an event took place. */ - private function record(Request $request, string $event) + private function record(Request $request, string $event): void { $this->traces[$this->getTraceKey($request)][] = $event; } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php index 0c9f10823a5c4..1bdaab148a9ee 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php @@ -54,6 +54,9 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface 'expires' => null, ]; + /** + * @return void + */ public function add(Response $response) { ++$this->embeddedResponses; @@ -95,6 +98,9 @@ public function add(Response $response) } } + /** + * @return void + */ public function update(Response $response) { // if we have no embedded Response, do nothing @@ -209,7 +215,7 @@ private function willMakeFinalResponseUncacheable(Response $response): bool * as cacheable in a public (shared) cache, but did not provide an explicit lifetime that would serve * as an upper bound. In this case, we can proceed and possibly keep the directive on the final response. */ - private function storeRelativeAgeDirective(string $directive, ?int $value, int $age, bool $isHeuristicallyCacheable) + private function storeRelativeAgeDirective(string $directive, ?int $value, int $age, bool $isHeuristicallyCacheable): void { if (null === $value) { if ($isHeuristicallyCacheable) { diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php index e282299aee8a3..33c8bd9412a8c 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php @@ -27,11 +27,15 @@ interface ResponseCacheStrategyInterface { /** * Adds a Response. + * + * @return void */ public function add(Response $response); /** * Updates the Response HTTP headers based on the embedded Responses. + * + * @return void */ public function update(Response $response); } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php index 4bd1cef0a7885..45a95552649ac 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php @@ -26,6 +26,9 @@ public function getName(): string return 'ssi'; } + /** + * @return void + */ public function addSurrogateControl(Response $response) { if (str_contains($response->getContent(), '#', $content, -1, \PREG_SPLIT_DELIM_CAPTURE); - $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); $i = 1; while (isset($chunks[$i])) { @@ -68,14 +73,10 @@ public function process(Request $request, Response $response): Response throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.'); } - $chunks[$i] = sprintf('surrogate->handle($this, %s, \'\', false) ?>'."\n", - var_export($options['virtual'], true) - ); - ++$i; - $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); - ++$i; + $chunks[$i] = $boundary.$options['virtual']."\n\n\n"; + $i += 2; } - $content = implode('', $chunks); + $content = $boundary.implode('', $chunks).$boundary; $response->setContent($content); $response->headers->set('X-Body-Eval', 'SSI'); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php index b21cb45874ea7..f838f1bfb444f 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php @@ -55,6 +55,8 @@ public function __construct(string $root, array $options = []) /** * Cleanups storage. + * + * @return void */ public function cleanup() { @@ -247,6 +249,8 @@ protected function generateContentDigest(Response $response): string /** * Invalidates all cache entries that match the request. * + * @return void + * * @throws \RuntimeException */ public function invalidate(Request $request) @@ -413,6 +417,9 @@ private function save(string $key, string $data, bool $overwrite = true): bool return true; } + /** + * @return string + */ public function getPath(string $key) { return $this->root.\DIRECTORY_SEPARATOR.substr($key, 0, 2).\DIRECTORY_SEPARATOR.substr($key, 2, 2).\DIRECTORY_SEPARATOR.substr($key, 4, 2).\DIRECTORY_SEPARATOR.substr($key, 6); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/StoreInterface.php b/src/Symfony/Component/HttpKernel/HttpCache/StoreInterface.php index 0cf8921cd19bb..b73cb7a9e688a 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/StoreInterface.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/StoreInterface.php @@ -41,6 +41,8 @@ public function write(Request $request, Response $response): string; /** * Invalidates all cache entries that match the request. + * + * @return void */ public function invalidate(Request $request); @@ -74,6 +76,8 @@ public function purge(string $url): bool; /** * Cleanups storage. + * + * @return void */ public function cleanup(); } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php b/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php index 6e9b17b316c10..e444458f73558 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php @@ -33,6 +33,8 @@ public function hasSurrogateCapability(Request $request): bool; /** * Adds Surrogate-capability to the given Request. + * + * @return void */ public function addSurrogateCapability(Request $request); @@ -40,6 +42,8 @@ public function addSurrogateCapability(Request $request); * Adds HTTP headers to specify that the Response needs to be parsed for Surrogate. * * This method only adds an Surrogate HTTP header if the Response has some Surrogate tags. + * + * @return void */ public function addSurrogateControl(Response $response); diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 7f5c592506c0f..794a55dc18f9c 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -92,6 +92,9 @@ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_R } } + /** + * @return void + */ public function terminate(Request $request, Response $response) { $this->dispatcher->dispatch(new TerminateEvent($this, $request, $response), KernelEvents::TERMINATE); @@ -100,7 +103,7 @@ public function terminate(Request $request, Response $response) /** * @internal */ - public function terminateWithException(\Throwable $exception, Request $request = null) + public function terminateWithException(\Throwable $exception, Request $request = null): void { if (!$request ??= $this->requestStack->getMainRequest()) { throw $exception; @@ -207,7 +210,7 @@ private function filterResponse(Response $response, Request $request, int $type) * operations such as {@link RequestStack::getParentRequest()} can lead to * weird results. */ - private function finishRequest(Request $request, int $type) + private function finishRequest(Request $request, int $type): void { $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); } diff --git a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php index 0938617169bc1..0f3630e7febdd 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php +++ b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php @@ -47,6 +47,8 @@ public function __construct(HttpKernelInterface $kernel, array $server = [], His /** * Sets whether to catch exceptions when the kernel is handling a request. + * + * @return void */ public function catchExceptions(bool $catchExceptions) { @@ -110,6 +112,9 @@ protected function getScript(object $request) return $code.$this->getHandleScript(); } + /** + * @return string + */ protected function getHandleScript() { return <<<'EOF' diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index e9694e978e3c5..c88671d8b4d13 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -17,6 +17,7 @@ use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\RemoveBuildParametersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; @@ -75,15 +76,15 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.2.10'; - public const VERSION_ID = 60210; + public const VERSION = '6.3.0-BETA1'; + public const VERSION_ID = 60300; public const MAJOR_VERSION = 6; - public const MINOR_VERSION = 2; - public const RELEASE_VERSION = 10; - public const EXTRA_VERSION = ''; + public const MINOR_VERSION = 3; + public const RELEASE_VERSION = 0; + public const EXTRA_VERSION = 'BETA1'; - public const END_OF_MAINTENANCE = '07/2023'; - public const END_OF_LIFE = '07/2023'; + public const END_OF_MAINTENANCE = '01/2024'; + public const END_OF_LIFE = '01/2024'; public function __construct(string $environment, bool $debug) { @@ -102,6 +103,9 @@ public function __clone() $this->resetServices = false; } + /** + * @return void + */ public function boot() { if (true === $this->booted) { @@ -130,6 +134,9 @@ public function boot() $this->booted = true; } + /** + * @return void + */ public function reboot(?string $warmupDir) { $this->shutdown(); @@ -137,6 +144,9 @@ public function reboot(?string $warmupDir) $this->boot(); } + /** + * @return void + */ public function terminate(Request $request, Response $response) { if (false === $this->booted) { @@ -148,6 +158,9 @@ public function terminate(Request $request, Response $response) } } + /** + * @return void + */ public function shutdown() { if (false === $this->booted) { @@ -280,7 +293,7 @@ public function getContainer(): ContainerInterface /** * @internal */ - public function setAnnotatedClassCache(array $annotatedClasses) + public function setAnnotatedClassCache(array $annotatedClasses): void { file_put_contents(($this->warmupDir ?: $this->getBuildDir()).'/annotations.map', sprintf('container = include $cachePath)) { $this->container = null; - } elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) { + } elseif (!$oldContainer || $this->container::class !== $oldContainer->name) { flock($lock, \LOCK_UN); fclose($lock); $this->container->set('kernel', $this); @@ -504,7 +523,7 @@ protected function initializeContainer() $this->container = require $cachePath; $this->container->set('kernel', $this); - if ($oldContainer && \get_class($this->container) !== $oldContainer->name) { + if ($oldContainer && $this->container::class !== $oldContainer->name) { // Because concurrent requests might still be using them, // old container files are not removed immediately, // but on a next dump of the container. @@ -526,7 +545,7 @@ protected function initializeContainer() $preload = array_merge($preload, (array) $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir'))); } - if ($preload && method_exists(Preloader::class, 'append') && file_exists($preloadFile = $buildDir.'/'.$class.'.preload.php')) { + if ($preload && file_exists($preloadFile = $buildDir.'/'.$class.'.preload.php')) { Preloader::append($preloadFile, $preload); } } @@ -591,6 +610,8 @@ protected function buildContainer(): ContainerBuilder /** * Prepares the ContainerBuilder before it is compiled. + * + * @return void */ protected function prepareContainer(ContainerBuilder $container) { @@ -642,18 +663,45 @@ protected function getContainerBuilder(): ContainerBuilder * * @param string $class The name of the class to generate * @param string $baseClass The name of the container's base class + * + * @return void */ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, string $class, string $baseClass) { // cache the container $dumper = new PhpDumper($container); + $buildParameters = []; + foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { + if ($pass instanceof RemoveBuildParametersPass) { + $buildParameters = array_merge($buildParameters, $pass->getRemovedParameters()); + } + } + + $inlineFactories = false; + if (isset($buildParameters['.container.dumper.inline_factories'])) { + $inlineFactories = $buildParameters['.container.dumper.inline_factories']; + } elseif ($container->hasParameter('container.dumper.inline_factories')) { + trigger_deprecation('symfony/http-kernel', '6.3', 'Parameter "%s" is deprecated, use ".%1$s" instead.', 'container.dumper.inline_factories'); + $inlineFactories = $container->getParameter('container.dumper.inline_factories'); + } + + $inlineClassLoader = $this->debug; + if (isset($buildParameters['.container.dumper.inline_class_loader'])) { + $inlineClassLoader = $buildParameters['.container.dumper.inline_class_loader']; + } elseif ($container->hasParameter('container.dumper.inline_class_loader')) { + trigger_deprecation('symfony/http-kernel', '6.3', 'Parameter "%s" is deprecated, use ".%1$s" instead.', 'container.dumper.inline_class_loader'); + $inlineClassLoader = $container->getParameter('container.dumper.inline_class_loader'); + } + $content = $dumper->dump([ 'class' => $class, 'base_class' => $baseClass, 'file' => $cache->getPath(), 'as_files' => true, 'debug' => $this->debug, + 'inline_factories' => $inlineFactories, + 'inline_class_loader' => $inlineClassLoader, 'build_time' => $container->hasParameter('kernel.container_build_time') ? $container->getParameter('kernel.container_build_time') : time(), 'preload_classes' => array_map('get_class', $this->bundles), ]); diff --git a/src/Symfony/Component/HttpKernel/KernelInterface.php b/src/Symfony/Component/HttpKernel/KernelInterface.php index 0959febf14e7b..14a053ab3004b 100644 --- a/src/Symfony/Component/HttpKernel/KernelInterface.php +++ b/src/Symfony/Component/HttpKernel/KernelInterface.php @@ -33,11 +33,15 @@ public function registerBundles(): iterable; /** * Loads the container configuration. + * + * @return void */ public function registerContainerConfiguration(LoaderInterface $loader); /** * Boots the current kernel. + * + * @return void */ public function boot(); @@ -45,6 +49,8 @@ public function boot(); * Shutdowns the kernel. * * This method is mainly useful when doing functional testing. + * + * @return void */ public function shutdown(); diff --git a/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php b/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php index 84452ae71fd7a..1940c80a902a0 100644 --- a/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php +++ b/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php @@ -44,6 +44,8 @@ public function countErrors(Request $request = null); /** * Removes all log records. + * + * @return void */ public function clear(); } diff --git a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php index d3c801de5b017..0fdbfc44bab16 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php @@ -87,6 +87,9 @@ public function find(?string $ip, ?string $url, ?int $limit, ?string $method, in return array_values($result); } + /** + * @return void + */ public function purge() { $flags = \FilesystemIterator::SKIP_DOTS; @@ -127,9 +130,7 @@ public function write(Profile $profile): bool // when there are errors in sub-requests, the parent and/or children tokens // may equal the profile token, resulting in infinite loops $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; - $childrenToken = array_filter(array_map(function (Profile $p) use ($profileToken) { - return $profileToken !== $p->getToken() ? $p->getToken() : null; - }, $profile->getChildren())); + $childrenToken = array_filter(array_map(fn (Profile $p) => $profileToken !== $p->getToken() ? $p->getToken() : null, $profile->getChildren())); // Store profile $data = [ @@ -165,11 +166,15 @@ public function write(Profile $profile): bool $profile->getIp(), $profile->getMethod(), $profile->getUrl(), - $profile->getTime(), + $profile->getTime() ?: time(), $profile->getParentToken(), $profile->getStatusCode(), ]); fclose($file); + + if (1 === mt_rand(1, 10)) { + $this->removeExpiredProfiles(); + } } return true; @@ -240,6 +245,9 @@ protected function readLineFromFile($file): mixed return '' === $line ? null : $line; } + /** + * @return Profile + */ protected function createProfileFromData(string $token, array $data, Profile $parent = null) { $profile = new Profile($token); @@ -289,4 +297,29 @@ private function doRead($token, Profile $profile = null): ?Profile return $this->createProfileFromData($token, $data, $profile); } + + private function removeExpiredProfiles(): void + { + $minimalProfileTimestamp = time() - 2 * 86400; + $file = $this->getIndexFilename(); + $handle = fopen($file, 'r'); + + if ($offset = is_file($file.'.offset') ? (int) file_get_contents($file.'.offset') : 0) { + fseek($handle, $offset); + } + + while ($line = fgets($handle)) { + [$csvToken, , , , $csvTime] = str_getcsv($line); + + if ($csvTime >= $minimalProfileTimestamp) { + break; + } + + @unlink($this->getFilename($csvToken)); + $offset += \strlen($line); + } + fclose($handle); + + file_put_contents($file.'.offset', $offset); + } } diff --git a/src/Symfony/Component/HttpKernel/Profiler/Profile.php b/src/Symfony/Component/HttpKernel/Profiler/Profile.php index 5ca2d96009534..8de1468ac6d35 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/Profile.php +++ b/src/Symfony/Component/HttpKernel/Profiler/Profile.php @@ -44,6 +44,9 @@ public function __construct(string $token) $this->token = $token; } + /** + * @return void + */ public function setToken(string $token) { $this->token = $token; @@ -59,6 +62,8 @@ public function getToken(): string /** * Sets the parent token. + * + * @return void */ public function setParent(self $parent) { @@ -89,6 +94,9 @@ public function getIp(): ?string return $this->ip; } + /** + * @return void + */ public function setIp(?string $ip) { $this->ip = $ip; @@ -102,6 +110,9 @@ public function getMethod(): ?string return $this->method; } + /** + * @return void + */ public function setMethod(string $method) { $this->method = $method; @@ -115,6 +126,9 @@ public function getUrl(): ?string return $this->url; } + /** + * @return void + */ public function setUrl(?string $url) { $this->url = $url; @@ -125,11 +139,17 @@ public function getTime(): int return $this->time ?? 0; } + /** + * @return void + */ public function setTime(int $time) { $this->time = $time; } + /** + * @return void + */ public function setStatusCode(int $statusCode) { $this->statusC F438 ode = $statusCode; @@ -154,6 +174,8 @@ public function getChildren(): array * Sets children profiler. * * @param Profile[] $children + * + * @return void */ public function setChildren(array $children) { @@ -165,6 +187,8 @@ public function setChildren(array $children) /** * Adds the child token. + * + * @return void */ public function addChild(self $child) { @@ -211,6 +235,8 @@ public function getCollectors(): array * Sets the Collectors associated with this profile. * * @param DataCollectorInterface[] $collectors + * + * @return void */ public function setCollectors(array $collectors) { @@ -222,6 +248,8 @@ public function setCollectors(array $collectors) /** * Adds a Collector. + * + * @return void */ public function addCollector(DataCollectorInterface $collector) { diff --git a/src/Symfony/Component/HttpKernel/Profiler/Profiler.php b/src/Symfony/Component/HttpKernel/Profiler/Profiler.php index 2bcabdfea4d3f..58508e5b48ad2 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/Profiler.php +++ b/src/Symfony/Component/HttpKernel/Profiler/Profiler.php @@ -46,6 +46,8 @@ public function __construct(ProfilerStorageInterface $storage, LoggerInterface $ /** * Disables the profiler. + * + * @return void */ public function disable() { @@ -54,6 +56,8 @@ public function disable() /** * Enables the profiler. + * + * @return void */ public function enable() { @@ -98,7 +102,7 @@ public function saveProfile(Profile $profile): bool } if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { - $this->logger->warning('Unable to store the profiler information.', ['configured_storage' => \get_class($this->storage)]); + $this->logger->warning('Unable to store the profiler information.', ['configured_storage' => $this->storage::class]); } return $ret; @@ -106,6 +110,8 @@ public function saveProfile(Profile $profile): bool /** * Purges all data from the storage. + * + * @return void */ public function purge() { @@ -162,6 +168,9 @@ public function collect(Request $request, Response $response, \Throwable $except return $profile; } + /** + * @return void + */ public function reset() { foreach ($this->collectors as $collector) { @@ -182,6 +191,8 @@ public function all(): array * Sets the Collectors associated with this profiler. * * @param DataCollectorInterface[] $collectors An array of collectors + * + * @return void */ public function set(array $collectors = []) { @@ -193,6 +204,8 @@ public function set(array $collectors = []) /** * Adds a Collector. + * + * @return void */ public function add(DataCollectorInterface $collector) { diff --git a/src/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php b/src/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php index 95d72f46b3872..247e51fce948c 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php +++ b/src/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php @@ -49,6 +49,8 @@ public function write(Profile $profile): bool; /** * Purges all data from the database. + * + * @return void */ public function purge(); } diff --git a/src/Symfony/Component/HttpKernel/RebootableInterface.php b/src/Symfony/Component/HttpKernel/RebootableInterface.php index e257237da90a1..e973f55400b8c 100644 --- a/src/Symfony/Component/HttpKernel/RebootableInterface.php +++ b/src/Symfony/Component/HttpKernel/RebootableInterface.php @@ -25,6 +25,8 @@ interface RebootableInterface * while building the container. Use the %kernel.build_dir% parameter instead. * * @param string|null $warmupDir pass null to reboot in the regular build directory + * + * @return void */ public function reboot(?string $warmupDir); } diff --git a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php index 44d962d503f3f..2670ce1ab8f00 100644 --- a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php +++ b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php @@ -7,8 +7,8 @@ '.$this->dumpHeader; } + /** + * @return void + */ public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) { if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) { @@ -788,6 +805,9 @@ public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) } } + /** + * @return void + */ public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild) { if (Cursor::HASH_OBJECT === $type) { @@ -816,6 +836,9 @@ public function enterHash(Cursor $cursor, int $type, string|int|null $class, boo } } + /** + * @return void + */ public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut) { $this->dumpEllipsis($cursor, $hasChild, $cut); @@ -862,7 +885,6 @@ protected function style(string $style, string $value, array $attr = []): string } elseif ('private' === $style) { $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); } - $map = static::$controlCharsMap; if (isset($attr['ellipsis'])) { $class = 'sf-dump-ellipsis'; @@ -881,6 +903,7 @@ protected function style(string $style, string $value, array $attr = []): string } } + $map = static::$controlCharsMap; $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { $s = $b = '\u{'.strtoupper(dechex(mb_ord($c[0]))).'}'; + }, $v); + } + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { $attr['href'] = $href; } @@ -917,6 +946,9 @@ protected function style(string $style, string $value, array $attr = []): string return $v; } + /** + * @return void + */ protected function dumpLine(int $depth, bool $endOfValue = false) { if (-1 === $this->lastDepth) { @@ -944,7 +976,7 @@ protected 10000 function dumpLine(int $depth, bool $endOfValue = false) AbstractDumper::dumpLine($depth); } - private function getSourceLink(string $file, int $line) + private function getSourceLink(string $file, int $line): string|false { $options = $this->extraDisplayOptions + $this->displayOptions; @@ -956,7 +988,7 @@ private function getSourceLink(string $file, int $line) } } -function esc(string $str) +function esc(string $str): string { return htmlspecialchars($str, \ENT_QUOTES, 'UTF-8'); } diff --git a/src/Symfony/Component/VarDumper/Dumper/ServerDumper.php b/src/Symfony/Component/VarDumper/Dumper/ServerDumper.php index f2c891b6a12c0..98c2149330106 100644 --- a/src/Symfony/Component/VarDumper/Dumper/ServerDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/ServerDumper.php @@ -41,10 +41,15 @@ public function getContextProviders(): array return $this->connection->getContextProviders(); } + /** + * @return string|null + */ public function dump(Data $data) { if (!$this->connection->write($data) && $this->wrappedDumper) { - $this->wrappedDumper->dump($data); + return $this->wrappedDumper->dump($data); } + + return null; } } diff --git a/src/Symfony/Component/VarDumper/Resources/functions/dump.php b/src/Symfony/Component/VarDumper/Resources/functions/dump.php index 6221a4d182f0f..31613f206b880 100644 --- a/src/Symfony/Component/VarDumper/Resources/functions/dump.php +++ b/src/Symfony/Component/VarDumper/Resources/functions/dump.php @@ -9,40 +9,52 @@ * file that was distributed with this source code. */ +use Symfony\Component\VarDumper\Caster\ScalarStub; use Symfony\Component\VarDumper\VarDumper; if (!function_exists('dump')) { /** * @author Nicolas Grekas + * @author Alexandre Daubois */ - function dump(mixed $var, mixed ...$moreVars): mixed + function dump(mixed ...$vars): mixed { - VarDumper::dump($var); + if (!$vars) { + VarDumper::dump(new ScalarStub('🐛')); - foreach ($moreVars as $v) { - VarDumper::dump($v); + return null; } - if (1 < func_num_args()) { - return func_get_args(); + if (isset($vars[0]) && 1 === count($vars)) { + VarDumper::dump($vars[0]); + $k = 0; + } else { + foreach ($vars as $k => $v) { + VarDumper::dump($v, is_int($k) ? 1 + $k : $k); + } } - return $var; + if (1 < count($vars)) { + return $vars; + } + + return $vars[$k]; } } if (!function_exists('dd')) { - /** - * @return never - */ - function dd(...$vars): void + function dd(mixed ...$vars): never { if (!in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !headers_sent()) { header('HTTP/1.1 500 Internal Server Error'); } - foreach ($vars as $v) { - VarDumper::dump($v); + if (isset($vars[0]) && 1 === count($vars)) { + VarDumper::dump($vars[0]); + } else { + foreach ($vars as $k => $v) { + VarDumper::dump($v, is_int($k) ? 1 + $k : $k); + } } exit(1); diff --git a/src/Symfony/Component/VarDumper/Server/Connection.php b/src/Symfony/Component/VarDumper/Server/Connection.php index c68408678f6a7..3c0f36864ff5c 100644 --- a/src/Symfony/Component/VarDumper/Server/Connection.php +++ b/src/Symfony/Component/VarDumper/Server/Connection.php @@ -62,7 +62,7 @@ public function write(Data $data): bool $context = array_filter($context); $encodedPayload = base64_encode(serialize([$data, $context]))."\n"; - set_error_handler([self::class, 'nullErrorHandler']); + set_error_handler(fn () => true); try { if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { return true; @@ -82,16 +82,14 @@ public function write(Data $data): bool return false; } - private static function nullErrorHandler(int $t, string $m) - { - // no-op - } - + /** + * @return resource|null + */ private function createSocket() { - set_error_handler([self::class, 'nullErrorHandler']); + set_error_handler(fn () => true); try { - return stream_socket_client($this->host, $errno, $errstr, 3); + return stream_socket_client($this->host, $errno, $errstr, 3) ?: null; } finally { restore_error_handler(); } diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php index cc3c8c745a4f1..66708944c63d6 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\VarDumper\Tests\Caster; use PHPUnit\Framework\TestCase; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; use Symfony\Component\VarDumper\Caster\Caster; use Symfony\Component\VarDumper\Caster\ExceptionCaster; @@ -340,8 +341,8 @@ public function testExcludeVerbosity() public function testAnonymous() { - $e = new \Exception(sprintf('Boo "%s" ba.', \get_class(new class('Foo') extends \Exception { - }))); + $e = new \Exception(sprintf('Boo "%s" ba.', (new class('Foo') extends \Exception { + })::class)); $expectedDump = <<<'EODUMP' Exception { @@ -354,4 +355,33 @@ public function testAnonymous() $this->assertDumpMatchesFormat($expectedDump, $e, Caster::EXCLUDE_VERBOSE); } + + /** + * @requires function \Symfony\Component\ErrorHandler\Exception\FlattenException::create + */ + public function testFlattenException() + { + $f = FlattenException::createFromThrowable(new \Exception('Hello')); + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => Symfony\Component\ErrorHandler\Exception\FlattenException { + -message: "Hello" + -code: 0 + -previous: null + -trace: array:%d %a + -traceAsString: ""…%d + -class: "Exception" + -statusCode: 500 + -statusText: "Internal Server Error" + -headers: [] + -file: "%sExceptionCasterTest.php" + -line: %d + -asString: null + } +] +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, [$f], Caster::EXCLUDE_VERBOSE); + } } diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php index 566de12a5e4eb..60203c46e2d32 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php @@ -17,14 +17,15 @@ /** * @author Nicolas Grekas * - * @requires extension redis - * * @group integration */ class RedisCasterTest extends TestCase { use VarDumperTestTrait; + /** + * @requires extension redis + */ public function testNotConnected() { $redis = new \Redis(); @@ -38,10 +39,18 @@ public function testNotConnected() $this->assertDumpMatchesFormat($xCast, $redis); } - public function testConnected() + /** + * @testWith ["Redis"] + * ["Relay\\Relay"] + */ + public function testConnected(string $class) { + if (!class_exists($class)) { + self::markTestSkipped(sprintf('"%s" class required', $class)); + } + $redisHost = explode(':', getenv('REDIS_HOST')) + [1 => 6379]; - $redis = new \Redis(); + $redis = new $class(); try { $redis->connect(...$redisHost); } catch (\Exception $e) { @@ -49,7 +58,7 @@ public function testConnected() } $xCast = <<assertDumpEquals($expected, $var); } + + public function testWeakMap() + { + $var = new \WeakMap(); + $obj = new \stdClass(); + $var[$obj] = 123; + + $expected = << { + object: {} + data: 123 + } + ] + } + EOTXT; + + $this->assertDumpEquals($expected, $var); + } } class MyArrayIterator extends \ArrayIterator diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php index cd6876cdff22f..8b3e12b5c1d72 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php @@ -15,6 +15,7 @@ use Symfony\Component\VarDumper\Caster\ArgsStub; use Symfony\Component\VarDumper\Caster\ClassStub; use Symfony\Component\VarDumper\Caster\LinkStub; +use Symfony\Component\VarDumper\Caster\ScalarStub; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Symfony\Component\VarDumper\Test\VarDumperTestTrait; @@ -87,6 +88,19 @@ public function testArgsStubWithClosure() $this->assertDumpMatchesFormat($expectedDump, $args); } + public function testEmptyStub() + { + $args = [new ScalarStub('🐛')]; + + $expectedDump = <<<'EODUMP' +array:1 [ + 0 => 🐛 +] +EODUMP; + + $this->assertDumpMatchesFormat($expectedDump, $args); + } + public function testLinkStub() { $var = [new LinkStub(__CLASS__, 0, __FILE__)]; @@ -192,8 +206,8 @@ public function testClassStubWithNotExistingMethod() public function testClassStubWithAnonymousClass() { - $var = [new ClassStub(\get_class(new class() extends \Exception { - }))]; + $var = [new ClassStub((new class() extends \Exception { + })::class)]; $cloner = new VarCloner(); $dumper = new HtmlDumper(); @@ -203,7 +217,7 @@ public function testClassStubWithAnonymousClass() $expectedDump = <<<'EODUMP' array:1 [ - 0 => "Exception@anonymous" + 0 => "Exception@anonymous" ] EODUMP; diff --git a/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php b/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php index 36639ca91b58e..ad1796999e2d9 100644 --- a/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php @@ -399,9 +399,7 @@ public function testJsonCast() public function testCaster() { $cloner = new VarCloner([ - '*' => function ($obj, $array) { - return ['foo' => 123]; - }, + '*' => fn ($obj, $array) => ['foo' => 123], __CLASS__ => function ($obj, $array) { ++$array['foo']; diff --git a/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php b/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php index 5941508a3a235..209be68f87457 100644 --- a/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php @@ -45,9 +45,7 @@ public function testDescribe(array $context, string $expectedOutput, bool $decor { $output = new BufferedOutput(); $output->setDecorated($decorated); - $descriptor = new CliDescriptor(new CliDumper(function ($s) { - return $s; - })); + $descriptor = new CliDescriptor(new CliDumper(fn ($s) => $s)); $descriptor->describe($output, new Data([[123]]), $context + ['timestamp' => 1544804268.3668], 1); diff --git a/src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php b/src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php index 25e9b1032e5c4..3321dc137e3c3 100644 --- a/src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php @@ -57,7 +57,7 @@ public function testGet() $this->assertStringMatchesFormat( << 1 0 => &1 null "const" => 1.1 @@ -72,6 +72,7 @@ public function testGet() é\\x01test\\t\\n ing """ + "bo\\u{FEFF}m" => "te\\u{FEFF}st" "[]" => [] "res" => stream resource {@{$res} %A wrapper_type: "plainfile" @@ -454,22 +455,4 @@ public function testDumpArrayWithColor($value, $flags, $expectedOut) $this->assertSame($expectedOut, $out); } - - private function getSpecialVars() - { - foreach (array_keys($GLOBALS) as $var) { - if ('GLOBALS' !== $var) { - unset($GLOBALS[$var]); - } - } - - $var = function &() { - $var = []; - $var[] = &$var; - - return $var; - }; - - return eval('return [$var(), $GLOBALS, &$GLOBALS];'); - } } diff --git a/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php b/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php index 7444d4bf58425..d158d7eb930aa 100644 --- a/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Dumper/FunctionsTest.php @@ -18,6 +18,17 @@ class FunctionsTest extends TestCase { + public function testDumpWithoutArg() + { + $this->setupVarDumper(); + + ob_start(); + $return = dump(); + ob_end_clean(); + + $this->assertNull($return); + } + public function testDumpReturnsFirstArg() { $this->setupVarDumper(); @@ -28,7 +39,20 @@ public function testDumpReturnsFirstArg() $return = dump($var1); ob_end_clean(); - $this->assertEquals($var1, $return); + $this->assertSame($var1, $return); + } + + public function testDumpReturnsFirstNamedArgWithoutSectionName() + { + $this->setupVarDumper(); + + $var1 = 'a'; + + ob_start(); + $return = dump(first: $var1); + ob_end_clean(); + + $this->assertSame($var1, $return); } public function testDumpReturnsAllArgsInArray() @@ -43,7 +67,22 @@ public function testDumpReturnsAllArgsInArray() $return = dump($var1, $var2, $var3); ob_end_clean(); - $this->assertEquals([$var1, $var2, $var3], $return); + $this->assertSame([$var1, $var2, $var3], $return); + } + + public function testDumpReturnsAllNamedArgsInArray() + { + $this->setupVarDumper(); + + $var1 = 'a'; + $var2 = 'b'; + $var3 = 'c'; + + ob_start(); + $return = dump($var1, second: $var2, third: $var3); + ob_end_clean(); + + $this->assertSame([$var1, 'second' => $var2, 'third' => $var3], $return); } protected function setupVarDumper() diff --git a/src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php b/src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php index 8c9592e47b304..2d0e41e2c6e75 100644 --- a/src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php @@ -54,7 +54,7 @@ public function testGet() $this->assertStringMatchesFormat( <<array:24 [ +array:25 [ "number" => 1 0 => &1 null "const" => 1.1 @@ -69,6 +69,7 @@ public function testGet() é\\x01test\\t\\n ing """ + "bo "te[]" => [] "res" => stream resource @{$res} %A wrapper_type: "plainfile" diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php index fc48012f4d13f..3896f31026d67 100644 --- a/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php +++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\VarDumper\Tests\Fixture; if (!class_exists(\Symfony\Component\VarDumper\Tests\Fixture\DumbFoo::class)) { @@ -17,8 +26,8 @@ class DumbFoo $var = [ 'number' => 1, null, - 'const' => 1.1, true, false, NAN, INF, -INF, PHP_INT_MAX, - 'str' => "déjà\n", "\xE9\x01test\t\ning", + 'const' => 1.1, true, false, \NAN, \INF, -\INF, \PHP_INT_MAX, + 'str' => "déjà\n", "\xE9\x01test\t\ning", "bo\u{feff}m" => "te\u{feff}st", '[]' => [], 'res' => $g, 'obj' => $foo, diff --git a/src/Symfony/Component/VarDumper/VarDumper.php b/src/Symfony/Component/VarDumper/VarDumper.php index 840bfd6496817..2e1dad116cdd9 100644 --- a/src/Symfony/Component/VarDumper/VarDumper.php +++ b/src/Symfony/Component/VarDumper/VarDumper.php @@ -37,13 +37,19 @@ class VarDumper */ private static $handler; - public static function dump(mixed $var) + /** + * @param string|null $label + * + * @return mixed + */ + public static function dump(mixed $var/* , string $label = null */) { + $label = 2 <= \func_num_args() ? func_get_arg(1) : null; if (null === self::$handler) { self::register(); } - return (self::$handler)($var); + return (self::$handler)($var, $label); } public static function setHandler(callable $callable = null): ?callable @@ -90,8 +96,14 @@ private static function register(): void $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); } - self::$handler = function ($var) use ($cloner, $dumper) { - $dumper->dump($cloner->cloneVar($var)); + self::$handler = function ($var, string $label = null) use ($cloner, $dumper) { + $var = $cloner->cloneVar($var); + + if (null !== $label) { + $var = $var->withContext(['label' => $label]); + } + + $dumper->dump($var); }; } diff --git a/src/Symfony/Component/VarDumper/composer.json b/src/Symfony/Component/VarDumper/composer.json index 71ec64c0de917..0c81f0da2ad4d 100644 --- a/src/Symfony/Component/VarDumper/composer.json +++ b/src/Symfony/Component/VarDumper/composer.json @@ -30,11 +30,6 @@ "phpunit/phpunit": "<5.4.3", "symfony/console": "<5.4" }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, "autoload": { "files": [ "Resources/functions/dump.php" ], "psr-4": { "Symfony\\Component\\VarDumper\\": "" }, diff --git a/src/Symfony/Component/VarExporter/Internal/Hydrator.php b/src/Symfony/Component/VarExporter/Internal/Hydrator.php index a1eac3d9e15a8..f665f6ee15c6e 100644 --- a/src/Symfony/Component/VarExporter/Internal/Hydrator.php +++ b/src/Symfony/Component/VarExporter/Internal/Hydrator.php @@ -254,6 +254,9 @@ public static function getSimpleHydrator($class) }; } + /** + * @return array + */ public static function getPropertyScopes($class) { $propertyScopes = []; diff --git a/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php b/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php index c78bdcf510c0b..baffe9bfb9c98 100644 --- a/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php +++ b/src/Symfony/Component/VarExporter/Internal/LazyObjectRegistry.php @@ -89,27 +89,23 @@ public static function getClassResetters($class) public static function getClassAccessors($class) { - return \Closure::bind(static function () { - return [ - 'get' => static function &($instance, $name, $readonly) { - if (!$readonly) { - return $instance->$name; - } - $value = $instance->$name; - - return $value; - }, - 'set' => static function ($instance, $name, $value) { - $instance->$name = $value; - }, - 'isset' => static function ($instance, $name) { - return isset($instance->$name); - }, - 'unset' => static function ($instance, $name) { - unset($instance->$name); - }, - ]; - }, null, \Closure::class === $class ? null : $class)(); + return \Closure::bind(static fn () => [ + 'get' => static function &($instance, $name, $readonly) { + if (!$readonly) { + return $instance->$name; + } + $value = $instance->$name; + + return $value; + }, + 'set' => static function ($instance, $name, $value) { + $instance->$name = $value; + }, + 'isset' => static fn ($instance, $name) => isset($instance->$name), + 'unset' => static function ($instance, $name) { + unset($instance->$name); + }, + ], null, \Closure::class === $class ? null : $class)(); } public static function getParentMethods($class) diff --git a/src/Symfony/Component/VarExporter/LazyGhostTrait.php b/src/Symfony/Component/VarExporter/LazyGhostTrait.php index 467e4d9105b8f..13e33f59c9bd8 100644 --- a/src/Symfony/Component/VarExporter/LazyGhostTrait.php +++ b/src/Symfony/Component/VarExporter/LazyGhostTrait.php @@ -232,7 +232,6 @@ public function __set($name, $value): void { $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); $scope = null; - $state = null; if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) { $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope); diff --git a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php index ae989ad68d904..0cbbd835b8f64 100644 --- a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php +++ b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php @@ -245,9 +245,7 @@ public function testPartialInitialization() public function testPartialInitializationWithReset() { - $initializer = static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) { - return 234; - }; + $initializer = static fn (ChildTestClass $instance, string $property, ?string $scope, mixed $default) => 234; $instance = ChildTestClass::createLazyGhost([ 'public' => $initializer, 'publicReadonly' => $initializer, @@ -279,9 +277,7 @@ public function testPartialInitializationWithReset() public function testPartialInitializationWithNastyPassByRef() { - $instance = ChildTestClass::createLazyGhost(['public' => function (ChildTestClass $instance, string &$property, ?string &$scope, mixed $default) { - return $property = $scope = 123; - }]); + $instance = ChildTestClass::createLazyGhost(['public' => fn (ChildTestClass $instance, string &$property, ?string &$scope, mixed $default) => $property = $scope = 123]); $this->assertSame(123, $instance->public); } @@ -314,9 +310,7 @@ public function testReflectionPropertyGetValue() public function testFullPartialInitialization() { $counter = 0; - $initializer = static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) use (&$counter) { - return 234; - }; + $initializer = static fn (ChildTestClass $instance, string $property, ?string $scope, mixed $default) => 234; $instance = ChildTestClass::createLazyGhost([ 'public' => $initializer, 'publicReadonly' => $initializer, diff --git a/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php index 58d49e5aa0404..c28c443b28e53 100644 --- a/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php +++ b/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php @@ -176,18 +176,14 @@ public function testDynamicProperty() public function testStringMagicGet() { - $proxy = $this->createLazyProxy(StringMagicGetClass::class, function () { - return new StringMagicGetClass(); - }); + $proxy = $this->createLazyProxy(StringMagicGetClass::class, fn () => new StringMagicGetClass()); $this->assertSame('abc', $proxy->abc); } public function testFinalPublicClass() { - $proxy = $this->createLazyProxy(FinalPublicClass::class, function () { - return new FinalPublicClass(); - }); + $proxy = $this->createLazyProxy(FinalPublicClass::class, fn () => new FinalPublicClass()); $this->assertSame(1, $proxy->increment()); $this->assertSame(2, $proxy->increment()); diff --git a/src/Symfony/Component/WebLink/EventListener/AddLinkHeaderListener.php b/src/Symfony/Component/WebLink/EventListener/AddLinkHeaderListener.php index 6af81a2e66da7..4e344cd2427cc 100644 --- a/src/Symfony/Component/WebLink/EventListener/AddLinkHeaderListener.php +++ b/src/Symfony/Component/WebLink/EventListener/AddLinkHeaderListener.php @@ -29,14 +29,12 @@ class_exists(HttpHeaderSerializer::class); */ class AddLinkHeaderListener implements EventSubscriberInterface { - private HttpHeaderSerializer $serializer; - - public function __construct() - { - $this->serializer = new HttpHeaderSerializer(); + public function __construct( + private readonly HttpHeaderSerializer $serializer = new HttpHeaderSerializer(), + ) { } - public function onKernelResponse(ResponseEvent $event) + public function onKernelResponse(ResponseEvent $event): void { if (!$event->isMainRequest()) { return; diff --git a/src/Symfony/Component/WebLink/Link.php b/src/Symfony/Component/WebLink/Link.php index 97a931b36823b..af32d21a010df 100644 --- a/src/Symfony/Component/WebLink/Link.php +++ b/src/Symfony/Component/WebLink/Link.php @@ -15,25 +15,128 @@ class Link implements EvolvableLinkInterface { - // Relations defined in https://www.w3.org/TR/html5/links.html#links and applicable on link elements + // @see https://www.iana.org/assignments/link-relations/link-relations.xhtml + public const REL_ABOUT = 'about'; + public const REL_ACL = 'acl'; public const REL_ALTERNATE = 'alternate'; + public const REL_AMPHTML = 'amphtml'; + public const REL_APPENDIX = 'appendix'; + public const REL_APPLE_TOUCH_ICON = 'apple-touch-icon'; + public const REL_APPLE_TOUCH_STARTUP_IMAGE = 'apple-touch-startup-image'; + public const REL_ARCHIVES = 'archives'; public const REL_AUTHOR = 'author'; + public const REL_BLOCKED_BY = 'blocked-by'; + public const REL_BOOKMARK = 'bookmark'; + public const REL_CANONICAL = 'canonical'; + public const REL_CHAPTER = 'chapter'; + public const REL_CITE_AS = 'cite-as'; + public const REL_COLLECTION = 'collection'; + public const REL_CONTENTS = 'contents'; + public const REL_CONVERTEDFROM = 'convertedfrom'; + public const REL_COPYRIGHT = 'copyright'; + public const REL_CREATE_FORM = 'create-form'; + public const REL_CURRENT = 'current'; + public const REL_DESCRIBEDBY = 'describedby'; + public const REL_DESCRIBES = 'describes'; + public const REL_DISCLOSURE = 'disclosure'; + public const REL_DNS_PREFETCH = 'dns-prefetch'; + public const REL_DUPLICATE = 'duplicate'; + public const REL_EDIT = 'edit'; + public const REL_EDIT_FORM = 'edit-form'; + public const REL_EDIT_MEDIA = 'edit-media'; + public const REL_ENCLOSURE = 'enclosure'; + public const REL_EXTERNAL = 'external'; + public const REL_FIRST = 'first'; + public const REL_GLOSSARY = 'glossary'; public const REL_HELP = 'help'; + public const REL_HOSTS = 'hosts'; + public const REL_HUB = 'hub'; public const REL_ICON = 'icon'; + public const REL_INDEX = 'index'; + public const REL_INTERVALAFTER = 'intervalafter'; + public const REL_INTERVALBEFORE = 'intervalbefore'; + public const REL_INTERVALCONTAINS = 'intervalcontains'; + public const REL_INTERVALDISJOINT = 'intervaldisjoint'; + public const REL_INTERVALDURING = 'intervalduring'; + public const REL_INTERVALEQUALS = 'intervalequals'; + public const REL_INTERVALFINISHEDBY = 'intervalfinishedby'; + public const REL_INTERVALFINISHES = 'intervalfinishes'; + public const REL_INTERVALIN = 'intervalin'; + public const REL_INTERVALMEETS = 'intervalmeets'; + public const REL_INTERVALMETBY = 'intervalmetby'; + public const REL_INTERVALOVERLAPPEDBY = 'intervaloverlappedby'; + public const REL_INTERVALOVERLAPS = 'intervaloverlaps'; + public const REL_INTERVALSTARTEDBY = 'intervalstartedby'; + public const REL_INTERVALSTARTS = 'intervalstarts'; + public const REL_ITEM = 'item'; + public const REL_LAST = 'last'; + public const REL_LATEST_VERSION = 'latest-version'; public const REL_LICENSE = 'license'; - public const REL_SEARCH = 'search'; - public const REL_STYLESHEET = 'stylesheet'; + public const REL_LINKSET = 'linkset'; + public const REL_LRDD = 'lrdd'; + public const REL_MANIFEST = 'manifest'; + public const REL_MASK_ICON = 'mask-icon'; + public const REL_MEDIA_FEED = 'media-feed'; + public const REL_MEMENTO = 'memento'; + public const REL_MICROPUB = 'micropub'; + public const REL_MODULEPRELOAD = 'modulepreload'; + public const REL_MONITOR = 'monitor'; + public const REL_MONITOR_GROUP = 'monitor-group'; public const REL_NEXT = 'next'; - public const REL_PREV = 'prev'; - - // Relation defined in https://www.w3.org/TR/preload/ - public const REL_PRELOAD = 'preload'; - - // Relations defined in https://www.w3.org/TR/resource-hints/ - public const REL_DNS_PREFETCH = 'dns-prefetch'; + public const REL_NEXT_ARCHIVE = 'next-archive'; + public const REL_NOFOLLOW = 'nofollow'; + public const REL_NOOPENER = 'noopener'; + public const REL_NOREFERRER = 'noreferrer'; + public const REL_OPENER = 'opener'; + public const REL_OPENID_2_LOCAL_ID = 'openid2.local_id'; + public const REL_OPENID_2_PROVIDER = 'openid2.provider'; + public const REL_ORIGINAL = 'original'; + public const REL_P_3_PV_1 = 'p3pv1'; + public const REL_PAYMENT = 'payment'; + public const REL_PINGBACK = 'pingback'; public const REL_PRECONNECT = 'preconnect'; + public const REL_PREDECESSOR_VERSION = 'predecessor-version'; public const REL_PREFETCH = 'prefetch'; + public const REL_PRELOAD = 'preload'; public const REL_PRERENDER = 'prerender'; + public const REL_PREV = 'prev'; + public const REL_PREVIEW = 'preview'; + public const REL_PREVIOUS = 'previous'; + public const REL_PREV_ARCHIVE = 'prev-archive'; + public const REL_PRIVACY_POLICY = 'privacy-policy'; + public const REL_PROFILE = 'profile'; + public const REL_PUBLICATION = 'publication'; + public const REL_RELATED = 'related'; + public const REL_RESTCONF = 'restconf'; + public const REL_REPLIES = 'replies'; + public const REL_RULEINPUT = 'ruleinput'; + public const REL_SEARCH = 'search'; + public const REL_SECTION = 'section'; + public const REL_SELF = 'self'; + public const REL_SERVICE = 'service'; + public const REL_SERVICE_DESC = 'service-desc'; + public const REL_SERVICE_DOC = 'service-doc'; + public const REL_SERVICE_META = 'service-meta'; + public const REL_SIPTRUNKINGCAPABILITY= 'siptrunkingcapability'; + public const REL_SPONSORED = 'sponsored'; + public const REL_START = 'start'; + public const REL_STATUS = 'status'; + public const REL_STYLESHEET = 'stylesheet'; + public const REL_SUBSECTION = 'subsection'; + public const REL_SUCCESSOR_VERSION = 'successor-version'; + public const REL_SUNSET = 'sunset'; + public const REL_TAG = 'tag'; + public const REL_TERMS_OF_SERVICE = 'terms-of-service'; + public const REL_TIMEGATE = 'timegate'; + public const REL_TIMEMAP = 'timemap'; + public const REL_TYPE = 'type'; + public const REL_UGC = 'ugc'; + public const REL_UP = 'up'; + public const REL_VERSION_HISTORY = 'version-history'; + public const REL_VIA = 'via'; + public const REL_WEBMENTION = 'webmention'; + public const REL_WORKING_COPY = 'working-copy'; + public const REL_WORKING_COPY_OF = 'working-copy-of'; // Extra relations public const REL_MERCURE = 'mercure'; diff --git a/src/Symfony/Component/WebLink/composer.json b/src/Symfony/Component/WebLink/composer.json index 34a2a35559718..cf13539db0758 100644 --- a/src/Symfony/Component/WebLink/composer.json +++ b/src/Symfony/Component/WebLink/composer.json @@ -22,9 +22,6 @@ "php": ">=8.1", "psr/link": "^1.1|^2.0" }, - "suggest": { - "symfony/http-kernel": "" - }, "require-dev": { "symfony/http-kernel": "^5.4|^6.0" }, diff --git a/src/Symfony/Component/Webhook/.gitattributes b/src/Symfony/Component/Webhook/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Webhook/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Webhook/.gitignore b/src/Symfony/Component/Webhook/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Webhook/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Webhook/CHANGELOG.md b/src/Symfony/Component/Webhook/CHANGELOG.md new file mode 100644 index 0000000000000..0634052525eae --- /dev/null +++ b/src/Symfony/Component/Webhook/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + + * Add the component (experimental) diff --git a/src/Symfony/Component/Webhook/Client/AbstractRequestParser.php b/src/Symfony/Component/Webhook/Client/AbstractRequestParser.php new file mode 100644 index 0000000000000..9e162931a48d7 --- /dev/null +++ b/src/Symfony/Component/Webhook/Client/AbstractRequestParser.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Client; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Webhook\Exception\RejectWebhookException; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +abstract class AbstractRequestParser implements RequestParserInterface +{ + public function parse(Request $request, string $secret): ?RemoteEvent + { + $this->validate($request); + + return $this->doParse($request, $secret); + } + + public function createSuccessfulResponse(): Response + { + return new Response('', 202); + } + + public function createRejectedResponse(string $reason): Response + { + return new Response($reason, 406); + } + + abstract protected function getRequestMatcher(): RequestMatcherInterface; + + abstract protected function doParse(Request $request, string $secret): ?RemoteEvent; + + protected function validate(Request $request): void + { + if (!$this->getRequestMatcher()->matches($request)) { + throw new RejectWebhookException(406, 'Request does not match.'); + } + } +} diff --git a/src/Symfony/Component/Webhook/Client/RequestParser.php b/src/Symfony/Component/Webhook/Client/RequestParser.php new file mode 100644 index 0000000000000..ec1c0e6ba7556 --- /dev/null +++ b/src/Symfony/Component/Webhook/Client/RequestParser.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Client; + +use Symfony\Component\HttpFoundation\ChainRequestMatcher; +use Symfony\Component\HttpFoundation\HeaderBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcher\IsJsonRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Webhook\Exception\RejectWebhookException; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +class RequestParser extends AbstractRequestParser +{ + public function __construct( + private readonly string $algo = 'sha256', + private readonly string $signatureHeaderName = 'Webhook-Signature', + private readonly string $eventHeaderName = 'Webhook-Event', + private readonly string $idHeaderName = 'Webhook-Id', + ) { + } + + protected function getRequestMatcher(): RequestMatcherInterface + { + return new ChainRequestMatcher([ + new MethodRequestMatcher('POST'), + new IsJsonRequestMatcher(), + ]); + } + + protected function doParse(Request $request, string $secret): RemoteEvent + { + $body = $request->toArray(); + + foreach ([$this->signatureHeaderName, $this->eventHeaderName, $this->idHeaderName] as $header) { + if (!$request->headers->has($header)) { + throw new RejectWebhookException(406, sprintf('Missing "%s" HTTP request signature header.', $header)); + } + } + + $this->validateSignature($request->headers, $request->getContent(), $secret); + + return new RemoteEvent( + $request->headers->get($this->eventHeaderName), + $request->headers->get($this->idHeaderName), + $body + ); + } + + private function validateSignature(HeaderBag $headers, string $body, $secret): void + { + $signature = $headers->get($this->signatureHeaderName); + $event = $headers->get($this->eventHeaderName); + $id = $headers->get($this->idHeaderName); + + if (!hash_equals($signature, $this->algo.'='.hash_hmac($this->algo, $event.$id.$body, $secret))) { + throw new RejectWebhookException(406, 'Signature is wrong.'); + } + } +} diff --git a/src/Symfony/Component/Webhook/Client/RequestParserInterface.php b/src/Symfony/Component/Webhook/Client/RequestParserInterface.php new file mode 100644 index 0000000000000..cff44ce8c0cf0 --- /dev/null +++ b/src/Symfony/Component/Webhook/Client/RequestParserInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Client; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Webhook\Exception\RejectWebhookException; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +interface RequestParserInterface +{ + /** + * Parses an HTTP Request and converts it into a RemoteEvent. + * + * @return ?RemoteEvent Returns null if the webhook must be ignored + * + * @throws RejectWebhookException When the payload is rejected (signature issue, parse issue, ...) + */ + public function parse(Request $request, string $secret): ?RemoteEvent; + + public function createSuccessfulResponse(): Response; + + public function createRejectedResponse(string $reason): Response; +} diff --git a/src/Symfony/Component/Webhook/Controller/WebhookController.php b/src/Symfony/Component/Webhook/Controller/WebhookController.php new file mode 100644 index 0000000000000..a31e9a5d08567 --- /dev/null +++ b/src/Symfony/Component/Webhook/Controller/WebhookController.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\RemoteEvent\Messenger\ConsumeRemoteEventMessage; +use Symfony\Component\Webhook\Client\RequestParserInterface; + +/** + * Receives webhooks from a variety of third-party providers. + * + * @author Fabien Potencier + * + * @experimental in 6.3 + * + * @internal + */ +final class WebhookController +{ + public function __construct( + /** @var array $parsers */ + private readonly array $parsers, + private readonly MessageBusInterface $bus, + ) { + } + + public function handle(string $type, Request $request): Response + { + if (!isset($this->parsers[$type])) { + return new Response(sprintf('No parser found for webhook of type "%s".', $type), 404); + } + /** @var RequestParserInterface $parser */ + $parser = $this->parsers[$type]['parser']; + + if (!$event = $parser->parse($request, $this->parsers[$type]['secret'])) { + return $parser->createRejectedResponse('Unable to parse the webhook payload.'); + } + + $this->bus->dispatch(new ConsumeRemoteEventMessage($type, $event)); + + return $parser->createSuccessfulResponse(); + } +} diff --git a/src/Symfony/Component/Webhook/Exception/ExceptionInterface.php b/src/Symfony/Component/Webhook/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000..0a5ac6d2e43f3 --- /dev/null +++ b/src/Symfony/Component/Webhook/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Exception; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/src/Symfony/Component/Webhook/Exception/InvalidArgumentException.php b/src/Symfony/Component/Webhook/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000..c7bfc15b52e24 --- /dev/null +++ b/src/Symfony/Component/Webhook/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Exception; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Webhook/Exception/LogicException.php b/src/Symfony/Component/Webhook/Exception/LogicException.php new file mode 100644 index 0000000000000..4755dd89b0048 --- /dev/null +++ b/src/Symfony/Component/Webhook/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Exception; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Webhook/Exception/RejectWebhookException.php b/src/Symfony/Component/Webhook/Exception/RejectWebhookException.php new file mode 100644 index 0000000000000..50efc0b4aee23 --- /dev/null +++ b/src/Symfony/Component/Webhook/Exception/RejectWebhookException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Exception; + +use Symfony\Component\HttpKernel\Exception\HttpException; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +class RejectWebhookException extends HttpException +{ + public function __construct(int $statusCode = 406, string $message = '', \Throwable $previous = null, array $headers = [], int $code = 0) + { + parent::__construct($statusCode, $message, $previous, $headers, $code); + } +} diff --git a/src/Symfony/Component/Webhook/Exception/RuntimeException.php b/src/Symfony/Component/Webhook/Exception/RuntimeException.php new file mode 100644 index 0000000000000..de167791ea28d --- /dev/null +++ b/src/Symfony/Component/Webhook/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Exception; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Webhook/LICENSE b/src/Symfony/Component/Webhook/LICENSE new file mode 100644 index 0000000000000..733c826ebcd63 --- /dev/null +++ b/src/Symfony/Component/Webhook/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Webhook/Messenger/SendWebhookHandler.php b/src/Symfony/Component/Webhook/Messenger/SendWebhookHandler.php new file mode 100644 index 0000000000000..d7cf44dda0e26 --- /dev/null +++ b/src/Symfony/Component/Webhook/Messenger/SendWebhookHandler.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Messenger; + +use Symfony\Component\Webhook\Server\TransportInterface; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +class SendWebhookHandler +{ + public function __construct( + private readonly TransportInterface $transport, + ) { + } + + public function __invoke(SendWebhookMessage $message): void + { + $this->transport->send($message->getSubscriber(), $message->getEvent()); + } +} diff --git a/src/Symfony/Component/Webhook/Messenger/SendWebhookMessage.php b/src/Symfony/Component/Webhook/Messenger/SendWebhookMessage.php new file mode 100644 index 0000000000000..e9e73f9321150 --- /dev/null +++ b/src/Symfony/Component/Webhook/Messenger/SendWebhookMessage.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Messenger; + +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Webhook\Subscriber; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +class SendWebhookMessage +{ + public function __construct( + private readonly Subscriber $subscriber, + private readonly RemoteEvent $event, + ) { + } + + public function getSubscriber(): Subscriber + { + return $this->subscriber; + } + + public function getEvent(): RemoteEvent + { + return $this->event; + } +} diff --git a/src/Symfony/Component/Webhook/README.md b/src/Symfony/Component/Webhook/README.md new file mode 100644 index 0000000000000..4ef92d1d52ed1 --- /dev/null +++ b/src/Symfony/Component/Webhook/README.md @@ -0,0 +1,18 @@ +Webhook Component +================= + +Symfony Webhook eases sending and consuming webhooks. + +**This Component is experimental**. +[Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html) +are not covered by Symfony's +[Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/webhook.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Webhook/Server/HeaderSignatureConfigurator.php b/src/Symfony/Component/Webhook/Server/HeaderSignatureConfigurator.php new file mode 100644 index 0000000000000..ced63cccb6aba --- /dev/null +++ b/src/Symfony/Component/Webhook/Server/HeaderSignatureConfigurator.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Server; + +use Symfony\Component\HttpClient\HttpOptions; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Webhook\Exception\LogicException; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +final class HeaderSignatureConfigurator implements RequestConfiguratorInterface +{ + public function __construct( + private readonly string $algo = 'sha256', + private readonly string $signatureHeaderName = 'Webhook-Signature', + ) { + } + + public function configure(RemoteEvent $event, string $secret, HttpOptions $options): void + { + $opts = $options->toArray(); + $headers = $opts['headers']; + if (!isset($opts['body'])) { + throw new LogicException('The body must be set.'); + } + $body = $opts['body']; + $headers[$this->signatureHeaderName] = $this->algo.'='.hash_hmac($this->algo, $event->getName().$event->getId().$body, $secret); + $options->setHeaders($headers); + } +} diff --git a/src/Symfony/Component/Webhook/Server/HeadersConfigurator.php b/src/Symfony/Component/Webhook/Server/HeadersConfigurator.php new file mode 100644 index 0000000000000..b51d463be0a2e --- /dev/null +++ b/src/Symfony/Component/Webhook/Server/HeadersConfigurator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Server; + +use Symfony\Component\HttpClient\HttpOptions; +use Symfony\Component\RemoteEvent\RemoteEvent; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +final class HeadersConfigurator implements RequestConfiguratorInterface +{ + public function __construct( + private readonly string $eventHeaderName = 'Webhook-Event', + private readonly string $idHeaderName = 'Webhook-Id', + ) { + } + + public function configure(RemoteEvent $event, string $secret, HttpOptions $options): void + { + $options->setHeaders([ + $this->eventHeaderName => $event->getName(), + $this->idHeaderName => $event->getId(), + ]); + } +} diff --git a/src/Symfony/Component/Webhook/Server/JsonBodyConfigurator.php b/src/Symfony/Component/Webhook/Server/JsonBodyConfigurator.php new file mode 100644 index 0000000000000..b1329e743a891 --- /dev/null +++ b/src/Symfony/Component/Webhook/Server/JsonBodyConfigurator.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Server; + +use Symfony\Component\HttpClient\HttpOptions; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +final class JsonBodyConfigurator implements RequestConfiguratorInterface +{ + public function __construct( + private readonly SerializerInterface $serializer, + ) { + } + + public function configure(RemoteEvent $event, string $secret, HttpOptions $options): void + { + $body = $this->serializer->serialize($event->getPayload(), 'json'); + $options->setBody($body); + $headers = $options->toArray()['headers']; + $headers['Content-Type'] = 'application/json'; + $options->setHeaders($headers); + } +} diff --git a/src/Symfony/Component/Webhook/Server/RequestConfiguratorInterface.php b/src/Symfony/Component/Webhook/Server/RequestConfiguratorInterface.php new file mode 100644 index 0000000000000..57d9f56b9ac42 --- /dev/null +++ b/src/Symfony/Component/Webhook/Server/RequestConfiguratorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Server; + +use Symfony\Component\HttpClient\HttpOptions; +use Symfony\Component\RemoteEvent\RemoteEvent; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +interface RequestConfiguratorInterface +{ + public function configure(RemoteEvent $event, string $secret, HttpOptions $options): void; +} diff --git a/src/Symfony/Component/Webhook/Server/Transport.php b/src/Symfony/Component/Webhook/Server/Transport.php new file mode 100644 index 0000000000000..c25b03d602ea0 --- /dev/null +++ b/src/Symfony/Component/Webhook/Server/Transport.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Server; + +use Symfony\Component\HttpClient\HttpOptions; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Webhook\Subscriber; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +class Transport implements TransportInterface +{ + public function __construct( + private readonly HttpClientInterface $client, + private readonly RequestConfiguratorInterface $headers, + private readonly RequestConfiguratorInterface $body, + private readonly RequestConfiguratorInterface $signer, + ) { + } + + public function send(Subscriber $subscriber, RemoteEvent $event): void + { + $options = new HttpOptions(); + + $this->headers->configure($event, $subscriber->getSecret(), $options); + $this->body->configure($event, $subscriber->getSecret(), $options); + $this->signer->configure($event, $subscriber->getSecret(), $options); + + $this->client->request('POST', $subscriber->getUrl(), $options->toArray()); + } +} diff --git a/src/Symfony/Component/Webhook/Server/TransportInterface.php b/src/Symfony/Component/Webhook/Server/TransportInterface.php new file mode 100644 index 0000000000000..55ff825358366 --- /dev/null +++ b/src/Symfony/Component/Webhook/Server/TransportInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Server; + +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Webhook\Subscriber; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +interface TransportInterface +{ + public function send(Subscriber $subscriber, RemoteEvent $event): void; +} diff --git a/src/Symfony/Component/Webhook/Subscriber.php b/src/Symfony/Component/Webhook/Subscriber.php new file mode 100644 index 0000000000000..ae39e6087b059 --- /dev/null +++ b/src/Symfony/Component/Webhook/Subscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook; + +class Subscriber +{ + public function __construct( + private readonly string $url, + #[\SensitiveParameter] private readonly string $secret, + ) { + } + + public function getUrl(): string + { + return $this->url; + } + + public function getSecret(): string + { + return $this->secret; + } +} diff --git a/src/Symfony/Component/Webhook/Test/AbstractRequestParserTestCase.php b/src/Symfony/Component/Webhook/Test/AbstractRequestParserTestCase.php new file mode 100644 index 0000000000000..e8cafb1c975b7 --- /dev/null +++ b/src/Symfony/Component/Webhook/Test/AbstractRequestParserTestCase.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Webhook\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Webhook\Client\RequestParserInterface; + +/** + * @author Fabien Potencier + * + * @experimental in 6.3 + */ +abstract class AbstractRequestParserTestCase extends TestCase +{ + /** + * @dataProvider getPayloads + */ + public function testParse(string $payload, RemoteEvent $expected) + { + $request = $this->createRequest($payload); + $parser = $this->createRequestParser(); + $wh = $parser->parse($request, $this->getSecret()); + $this->assertEquals($expected, $wh); + } + + public static function getPayloads(): iterable + { + $currentDir = \dirname((new \ReflectionClass(static::class))->getFileName()); + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($currentDir.'/Fixtures', \RecursiveDirectoryIterator::SKIP_DOTS)) as $file) { + $filename = str_replace($currentDir.'/Fixtures/', '', $file->getPathname()); + if (static::getFixtureExtension() !== pathinfo($filename, \PATHINFO_EXTENSION)) { + continue; + } + + yield $filename => [ + file_get_contents($file), + include(str_replace('.'.static::getFixtureExtension(), '.php', $file->getPathname())), + ]; + } + } + + abstract protected function createRequestParser(): RequestParserInterface; + + protected function getSecret(): string + { + return ''; + } + + protected function createRequest(string $payload): Request + { + return Request::create('/', 'POST', [], [], [], [ + 'Content-Type' => 'application/json', + ], $payload); + } + + protected static function getFixtureExtension(): string + { + return 'json'; + } +} diff --git a/src/Symfony/Component/Webhook/Tests/Client/RequestParserTest.php b/src/Symfony/Component/Webhook/Tests/Client/RequestParserTest.php new file mode 100644 index 0000000000000..18dbe5c1ff616 --- /dev/null +++ b/src/Symfony/Component/Webhook/Tests/Client/RequestParserTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source cod 97AE e. + */ + +namespace Symfony\Component\Webhook\Tests\Client; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Webhook\Client\RequestParser; +use Symfony\Component\Webhook\Exception\RejectWebhookException; + +class RequestParserTest extends TestCase +{ + public function testParseDoesNotMatch() + { + $this->expectException(RejectWebhookException::class); + + $request = new Request(); + $parser = new RequestParser(); + $parser->parse($request, '$ecret'); + } +} diff --git a/src/Symfony/Component/Webhook/composer.json b/src/Symfony/Component/Webhook/composer.json new file mode 100644 index 0000000000000..3a33faf7cc350 --- /dev/null +++ b/src/Symfony/Component/Webhook/composer.json @@ -0,0 +1,32 @@ +{ + "name": "symfony/webhook", + "type": "library", + "description": "Eases sending and consuming webhooks", + "keywords": ["webhook"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-foundation": "^6.3", + "symfony/http-kernel": "^6.3", + "symfony/messenger": "^5.4|^6.1", + "symfony/remote-event": "^6.3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Webhook\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Webhook/phpunit.xml.dist b/src/Symfony/Component/Webhook/phpunit.xml.dist new file mode 100644 index 0000000000000..ff3020250d20c --- /dev/null +++ b/src/Symfony/Component/Webhook/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Tests + ./vendor + + + diff --git a/src/Symfony/Component/Workflow/Definition.php b/src/Symfony/Component/Workflow/Definition.php index c3a0637b0c59a..cdb180976895e 100644 --- a/src/Symfony/Component/Workflow/Definition.php +++ b/src/Symfony/Component/Workflow/Definition.php @@ -76,7 +76,7 @@ public function getMetadataStore(): MetadataStoreInterface return $this->metadataStore; } - private function setInitialPlaces(string|array $places = null) + private function setInitialPlaces(string|array $places = null): void { if (1 > \func_num_args()) { trigger_deprecation('symfony/workflow', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); @@ -96,7 +96,7 @@ private function setInitialPlaces(string|array $places = null) $this->initialPlaces = $places; } - private function addPlace(string $place) + private function addPlace(string $place): void { if (!\count($this->places)) { $this->initialPlaces = [$place]; @@ -105,7 +105,7 @@ private function addPlace(string $place) $this->places[$place] = $place; } - private function addTransition(Transition $transition) + private function addTransition(Transition $transition): void { $name = $transition->getName(); diff --git a/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php b/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php index c87e93a2fe831..6c7b5d93b3b92 100644 --- a/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php +++ b/src/Symfony/Component/Workflow/Dumper/MermaidDumper.php @@ -102,21 +102,9 @@ public function dump(Definition $definition, Marking $marking = null, array $opt $to = $placeNameMap[$to]; if (self::TRANSITION_TYPE_STATEMACHINE === $this->transitionType) { - $transitionOutput = $this->styleStatemachineTransition( - $from, - $to, - $transitionId, - $transitionLabel, - $transitionMeta - ); + $transitionOutput = $this->styleStatemachineTransition($from, $to, $transitionLabel, $transitionMeta); } else { - $transitionOutput = $this->styleWorkflowTransition( - $from, - $to, - $transitionId, - $transitionLabel, - $transitionMeta - ); + $transitionOutput = $this->styleWorkflowTransition($from, $to, $transitionId, $transitionLabel, $transitionMeta); } foreach ($transitionOutput as $line) { @@ -187,7 +175,7 @@ private function styleNode(array $meta, string $nodeName, bool $hasMarking = fal * Replace double quotes with the mermaid escape syntax and * ensure all other characters are properly escaped. */ - private function escape(string $label) + private function escape(string $label): string { $label = str_replace('"', '#quot;', $label); @@ -208,13 +196,8 @@ private function validateTransitionType(string $transitionType): void } } - private function styleStatemachineTransition( - string $from, - string $to, - int $transitionId, - string $transitionLabel, - array $transitionMeta - ): array { + private function styleStatemachineTransition(string $from, string $to, string $transitionLabel, array $transitionMeta): array + { $transitionOutput = [sprintf('%s-->|%s|%s', $from, str_replace("\n", ' ', $this->escape($transitionLabel)), $to)]; $linkStyle = $this->styleLink($transitionMeta); @@ -227,13 +210,8 @@ private function styleStatemachineTransition( return $transitionOutput; } - private function styleWorkflowTransition( - string $from, - string $to, - int $transitionId, - string $transitionLabel, - array $transitionMeta - ) { + private function styleWorkflowTransition(string $from, string $to, int $transitionId, string $transitionLabel, array $transitionMeta): array + { $transitionOutput = []; $transitionLabel = $this->escape($transitionLabel); diff --git a/src/Symfony/Component/Workflow/Event/Event.php b/src/Symfony/Component/Workflow/Event/Event.php index cd7fab7896b20..5cd31e9154017 100644 --- a/src/Symfony/Component/Workflow/Event/Event.php +++ b/src/Symfony/Component/Workflow/Event/Event.php @@ -75,6 +75,9 @@ public function getWorkflowName() return $this->workflow->getName(); } + /** + * @return mixed + */ public function getMetadata(string $key, string|Transition|null $subject) { return $this->workflow->getMetadataStore()->getMetadata($key, $subject); diff --git a/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php b/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php index 8a7ea374c90aa..8d82824eb0f0f 100644 --- a/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php +++ b/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php @@ -27,22 +27,31 @@ public function __construct(LoggerInterface $logger) $this->logger = $logger; } + /** + * @return void + */ public function onLeave(Event $event) { foreach ($event->getTransition()->getFroms() as $place) { - $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } + /** + * @return void + */ public function onTransition(Event $event) { - $this->logger->info(sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), $event->getSubject()::class, $event->getWorkflowName())); } + /** + * @return void + */ public function onEnter(Event $event) { foreach ($event->getTransition()->getTos() as $place) { - $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } diff --git a/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php b/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php index 6fb43077bb7a4..82fe1651522ca 100644 --- a/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php +++ b/src/Symfony/Component/Workflow/EventListener/ExpressionLanguage.php @@ -22,21 +22,18 @@ */ class ExpressionLanguage extends BaseExpressionLanguage { + /** + * @return void + */ protected function registerFunctions() { parent::registerFunctions(); - $this->register('is_granted', function ($attributes, $object = 'null') { - return sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object); - }, function (array $variables, $attributes, $object = null) { - return $variables['auth_checker']->isGranted($attributes, $object); - }); + $this->register('is_granted', fn ($attributes, $object = 'null') => sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object), fn (array $variables, $attributes, $object = null) => $variables['auth_checker']->isGranted($attributes, $object)); - $this->register('is_valid', function ($object = 'null', $groups = 'null') { - return sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups); - }, function (array $variables, $object = null, $groups = null) { + $this->register('is_valid', fn ($object = 'null', $groups = 'null') => sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups), function (array $variables, $object = null, $groups = null) { if (!$variables['validator'] instanceof ValidatorInterface) { - throw new RuntimeException('"is_valid" cannot be used as the Validator component is not installed.'); + throw new RuntimeException('"is_valid" cannot be used as the Validator component is not installed. Try running "composer require symfony/validator".'); } $errors = $variables['validator']->validate($object, null, $groups); diff --git a/src/Symfony/Component/Workflow/EventListener/GuardExpression.php b/src/Symfony/Component/Workflow/EventListener/GuardExpression.php index 9fb152567bff9..23e830cac4804 100644 --- a/src/Symfony/Component/Workflow/EventListener/GuardExpression.php +++ b/src/Symfony/Component/Workflow/EventListener/GuardExpression.php @@ -24,11 +24,17 @@ public function __construct(Transition $transition, string $expression) $this->expression = $expression; } + /** + * @return Transition + */ public function getTransition() { return $this->transition; } + /** + * @return string + */ public function getExpression() { return $this->expression; diff --git a/src/Symfony/Component/Workflow/EventListener/GuardListener.php b/src/Symfony/Component/Workflow/EventListener/GuardListener.php index 5c873d8b99e9e..c207b1a655daf 100644 --- a/src/Symfony/Component/Workflow/EventListener/GuardListener.php +++ b/src/Symfony/Component/Workflow/EventListener/GuardListener.php @@ -43,6 +43,9 @@ public function __construct(array $configuration, ExpressionLanguage $expression $this->validator = $validator; } + /** + * @return void + */ public function onTransition(GuardEvent $event, string $eventName) { if (!isset($this->configuration[$eventName])) { @@ -62,7 +65,7 @@ public function onTransition(GuardEvent $event, string $eventName) } } - private function validateGuardExpression(GuardEvent $event, string $expression) + private function validateGuardExpression(GuardEvent $event, string $expression): void { if (!$this->expressionLanguage->evaluate($expression, $this->getVariables($event))) { $blocker = TransitionBlocker::createBlockedByExpressionGuardListener($expression); diff --git a/src/Symfony/Component/Workflow/Exception/TransitionException.php b/src/Symfony/Component/Workflow/Exception/TransitionException.php index d493e22343f39..890d8e244b5d9 100644 --- a/src/Symfony/Component/Workflow/Exception/TransitionException.php +++ b/src/Symfony/Component/Workflow/Exception/TransitionException.php @@ -34,6 +34,9 @@ public function __construct(object $subject, string $transitionName, WorkflowInt $this->context = $context; } + /** + * @return object + */ public function getSubject() { return $this->subject; diff --git a/src/Symfony/Component/Workflow/Marking.php b/src/Symfony/Component/Workflow/Marking.php index 36deb2d4086c6..95a83f0cfd243 100644 --- a/src/Symfony/Component/Workflow/Marking.php +++ b/src/Symfony/Component/Workflow/Marking.php @@ -31,21 +31,33 @@ public function __construct(array $representation = []) } } + /** + * @return void + */ public function mark(string $place) { $this->places[$place] = 1; } + /** + * @return void + */ public function unmark(string $place) { unset($this->places[$place]); } + /** + * @return bool + */ public function has(string $place) { return isset($this->places[$place]); } + /** + * @return array + */ public function getPlaces() { return $this->places; diff --git a/src/Symfony/Component/Workflow/MarkingStore/MarkingStoreInterface.php b/src/Symfony/Component/Workflow/MarkingStore/MarkingStoreInterface.php index 99cb72c7cec71..7547a7f43b721 100644 --- a/src/Symfony/Component/Workflow/MarkingStore/MarkingStoreInterface.php +++ b/src/Symfony/Component/Workflow/MarkingStore/MarkingStoreInterface.php @@ -31,6 +31,8 @@ public function getMarking(object $subject): Marking; /** * Sets a Marking to a subject. + * + * @return void */ public function setMarking(object $subject, Marking $marking, array $context = []); } diff --git a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php index b1602dc72e459..78d3307e6ac6c 100644 --- a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php +++ b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php @@ -74,7 +74,7 @@ public function getMarking(object $subject): Marking return new Marking($marking); } - public function setMarking(object $subject, Marking $marking, array $context = []) + public function setMarking(object $subject, Marking $marking, array $context = []): void { $marking = $marking->getPlaces(); diff --git a/src/Symfony/Component/Workflow/Metadata/GetMetadataTrait.php b/src/Symfony/Component/Workflow/Metadata/GetMetadataTrait.php index 9eddfd8b4be03..286e2f8605b2d 100644 --- a/src/Symfony/Component/Workflow/Metadata/GetMetadataTrait.php +++ b/src/Symfony/Component/Workflow/Metadata/GetMetadataTrait.php @@ -18,6 +18,9 @@ */ trait GetMetadataTrait { + /** + * @return mixed + */ public function getMetadata(string $key, string|Transition $subject = null) { if (null === $subject) { diff --git a/src/Symfony/Component/Workflow/Metadata/MetadataStoreInterface.php b/src/Symfony/Component/Workflow/Metadata/MetadataStoreInterface.php index 8b2886f2f648e..9acd540dcc60d 100644 --- a/src/Symfony/Component/Workflow/Metadata/MetadataStoreInterface.php +++ b/src/Symfony/Component/Workflow/Metadata/MetadataStoreInterface.php @@ -34,6 +34,8 @@ public function getTransitionMetadata(Transition $transition): array; * @param string|Transition|null $subject Use null to get workflow metadata * Use a string (the place name) to get place metadata * Use a Transition instance to get transition metadata + * + * @return mixed */ public function getMetadata(string $key, string|Transition $subject = null); } diff --git a/src/Symfony/Component/Workflow/Registry.php b/src/Symfony/Component/Workflow/Registry.php index a9c21af18a906..287d8b750f9b4 100644 --- a/src/Symfony/Component/Workflow/Registry.php +++ b/src/Symfony/Component/Workflow/Registry.php @@ -24,6 +24,9 @@ class Registry { private array $workflows = []; + /** + * @return void + */ public function addWorkflow(WorkflowInterface $workflow, WorkflowSupportStrategyInterface $supportStrategy) { $this->workflows[] = [$workflow, $supportStrategy]; @@ -55,9 +58,7 @@ public function get(object $subject, string $workflowName = null): Workflow } if (2 <= \count($matched)) { - $names = array_map(static function (WorkflowInterface $workflow): string { - return $workflow->getName(); - }, $matched); + $names = array_map(static fn (WorkflowInterface $workflow): string => $workflow->getName(), $matched); throw new InvalidArgumentException(sprintf('Too many workflows (%s) match this subject (%s); set a different name on each and use the second (name) argument of this method.', implode(', ', $names), get_debug_type($subject))); } diff --git a/src/Symfony/Component/Workflow/Tests/Dumper/GraphvizDumperTest.php b/src/Symfony/Component/Workflow/Tests/Dumper/GraphvizDumperTest.php index f18868fcc3b41..ff38822d2235a 100644 --- a/src/Symfony/Component/Workflow/Tests/Dumper/GraphvizDumperTest.php +++ b/src/Symfony/Component/Workflow/Tests/Dumper/GraphvizDumperTest.php @@ -47,7 +47,7 @@ public function testDumpWithMarking($definition, $marking, $expected) $this->assertEquals($expected, $dump); } - public static function provideWorkflowDefinitionWithMarking() + public static function provideWorkflowDefinitionWithMarking(): \Generator { yield [ self::createComplexWorkflowDefinition(), @@ -62,13 +62,13 @@ public static function provideWorkflowDefinitionWithMarking() ]; } - public static function provideWorkflowDefinitionWithoutMarking() + public static function provideWorkflowDefinitionWithoutMarking(): \Generator { yield [self::createComplexWorkflowDefinition(), self::provideComplexWorkflowDumpWithoutMarking()]; yield [self::createSimpleWorkflowDefinition(), self::provideSimpleWorkflowDumpWithoutMarking()]; } - public static function createComplexWorkflowDefinitionDumpWithMarking() + public static function createComplexWorkflowDefinitionDumpWithMarking(): string { return 'digraph workflow { ratio="compress" rankdir="LR" @@ -106,7 +106,7 @@ public static function createComplexWorkflowDefinitionDumpWithMarking() '; } - public static function createSimpleWorkflowDumpWithMarking() + public static function createSimpleWorkflowDumpWithMarking(): string { return 'digraph workflow { ratio="compress" rankdir="LR" @@ -126,7 +126,7 @@ public static function createSimpleWorkflowDumpWithMarking() '; } - public static function provideComplexWorkflowDumpWithoutMarking() + public static function provideComplexWorkflowDumpWithoutMarking(): string { return 'digraph workflow { ratio="compress" rankdir="LR" @@ -164,7 +164,7 @@ public static function provideComplexWorkflowDumpWithoutMarking() '; } - public static function provideSimpleWorkflowDumpWithoutMarking() + public static function provideSimpleWorkflowDumpWithoutMarking(): string { return 'digraph workflow { ratio="compress" rankdir="LR" diff --git a/src/Symfony/Component/Workflow/Tests/Dumper/MermaidDumperTest.php b/src/Symfony/Component/Workflow/Tests/Dumper/MermaidDumperTest.php index 93c1e339486ee..01b5638b2745b 100644 --- a/src/Symfony/Component/Workflow/Tests/Dumper/MermaidDumperTest.php +++ b/src/Symfony/Component/Workflow/Tests/Dumper/MermaidDumperTest.php @@ -146,7 +146,7 @@ public static function provideWorkflowDefinitionWithoutMarking(): array ]; } - public static function provideWorkflowWithReservedWords() + public static function provideWorkflowWithReservedWords(): array { $builder = new DefinitionBuilder(); diff --git a/src/Symfony/Component/Workflow/Tests/Dumper/PlantUmlDumperTest.php b/src/Symfony/Component/Workflow/Tests/Dumper/PlantUmlDumperTest.php index 6af7809ae0ad5..6e1b30340a2c2 100644 --- a/src/Symfony/Component/Workflow/Tests/Dumper/PlantUmlDumperTest.php +++ b/src/Symfony/Component/Workflow/Tests/Dumper/PlantUmlDumperTest.php @@ -36,7 +36,7 @@ public function testDumpWorkflowWithoutMarking($definition, $marking, $expectedF $this->assertStringEqualsFile($file, $dump); } - public static function provideWorkflowDefinitionWithoutMarking() + public static function provideWorkflowDefinitionWithoutMarking(): \Generator { yield [self::createSimpleWorkflowDefinition(), null, 'simple-workflow-nomarking', 'SimpleDiagram']; yield [self::createComplexWorkflowDefinition(), null, 'complex-workflow-nomarking', 'ComplexDiagram']; @@ -59,7 +59,7 @@ public function testDumpStateMachineWithoutMarking($definition, $marking, $expec $this->assertStringEqualsFile($file, $dump); } - public static function provideStateMachineDefinitionWithoutMarking() + public static function provideStateMachineDefinitionWithoutMarking(): \Generator { yield [static::createComplexStateMachineDefinition(), null, 'complex-state-machine-nomarking', 'SimpleDiagram']; $marking = new Marking(['c' => 1, 'e' => 1]); @@ -94,7 +94,7 @@ public function testDumpWorkflowWithSpacesInTheStateNamesAndDescription() $this->assertStringEqualsFile($file, $dump); } - private function getFixturePath($name, $transitionType) + private function getFixturePath($name, $transitionType): string { return __DIR__.'/../fixtures/puml/'.$transitionType.'/'.$name.'.puml'; } diff --git a/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php b/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php index 26004dfdd728c..a5ff6a01c86fa 100644 --- a/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php +++ b/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php @@ -143,7 +143,7 @@ public function testGuardExpressionBlocks() $this->assertTrue($event->isBlocked()); } - private function createEvent(Transition $transition = null) + private function createEvent(Transition $transition = null): GuardEvent { $subject = new Subject(); $transition ??= new Transition('name', 'from', 'to'); @@ -171,7 +171,7 @@ private function configureAuthenticationChecker($isUsed, $granted = true) ; } - private function configureValidator($isUsed, $valid = true) + private function configureValidator($isUsed, $valid = true): void { if (!$isUsed) { $this->validator diff --git a/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php b/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php index 4691e119cf9e0..34dbd3bd2d24f 100644 --- a/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -111,7 +111,7 @@ public function testGetMarkingWithUninitializedProperty2() $markingStore->getMarking($subject); } - private function createValueObject(string $markingValue) + private function createValueObject(string $markingValue): object { return new class($markingValue) { /** @var string */ diff --git a/src/Symfony/Component/Workflow/Tests/RegistryTest.php b/src/Symfony/Component/Workflow/Tests/RegistryTest.php index a0d9c0d1f1991..eb9e83b5027b1 100644 --- a/src/Symfony/Component/Workflow/Tests/RegistryTest.php +++ b/src/Symfony/Component/Workflow/Tests/RegistryTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Workflow\Tests; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Exception\InvalidArgumentException; @@ -108,13 +109,11 @@ public function testAllWithNoMatch() $this->assertCount(0, $workflows); } - private function createWorkflowSupportStrategy($supportedClassName) + private function createWorkflowSupportStrategy($supportedClassName): MockObject&WorkflowSupportStrategyInterface { $strategy = $this->createMock(WorkflowSupportStrategyInterface::class); $strategy->expects($this->any())->method('supports') - ->willReturnCallback(function ($workflow, $subject) use ($supportedClassName) { - return $subject instanceof $supportedClassName; - }); + ->willReturnCallback(fn ($workflow, $subject) => $subject instanceof $supportedClassName); return $strategy; } diff --git a/src/Symfony/Component/Workflow/Tests/Subject.php b/src/Symfony/Component/Workflow/Tests/Subject.php index dd63da99d5f9c..6dd76e1ec51b7 100644 --- a/src/Symfony/Component/Workflow/Tests/Subject.php +++ b/src/Symfony/Component/Workflow/Tests/Subject.php @@ -22,12 +22,12 @@ public function __construct($marking = null) $this->context = []; } - public function getMarking() + public function getMarking(): string|array|null { return $this->marking; } - public function setMarking($marking, array $context = []) + public function setMarking($marking, array $context = []): void { $this->marking = $marking; $this->context = $context; diff --git a/src/Symfony/Component/Workflow/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php b/src/Symfony/Component/Workflow/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php index 8a5c300ade3e0..48a455e5a6189 100644 --- a/src/Symfony/Component/Workflow/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php +++ b/src/Symfony/Component/Workflow/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Workflow\Tests\SupportStrategy; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\SupportStrategy\InstanceOfSupportStrategy; use Symfony\Component\Workflow\Workflow; @@ -31,7 +32,7 @@ public function testSupportsIfNotClassInstance() $this->assertFalse($strategy->supports($this->createWorkflow(), new Subject1())); } - private function createWorkflow() + private function createWorkflow(): MockObject&Workflow { return $this->getMockBuilder(Workflow::class) ->disableOriginalConstructor() diff --git a/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php b/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php index ae812324c6395..07a589e47b04b 100644 --- a/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php +++ b/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php @@ -17,7 +17,7 @@ trait WorkflowBuilderTrait { - private static function createComplexWorkflowDefinition() + private static function createComplexWorkflowDefinition(): Definition { $places = range('a', 'g'); @@ -52,7 +52,7 @@ private static function createComplexWorkflowDefinition() // +----+ +----+ +----+ +----+ } - private static function createSimpleWorkflowDefinition() + private static function createSimpleWorkflowDefinition(): Definition { $places = range('a', 'c'); @@ -87,7 +87,7 @@ private static function createSimpleWorkflowDefinition() // +---+ +----+ +---+ +----+ +---+ } - private static function createWorkflowWithSameNameTransition() + private static function createWorkflowWithSameNameTransition(): Definition { $places = range('a', 'c'); @@ -115,7 +115,7 @@ private static function createWorkflowWithSameNameTransition() // +--------------------------------------------------------------------+ } - private static function createComplexStateMachineDefinition() + private static function createComplexStateMachineDefinition(): Definition { $places = ['a', 'b', 'c', 'd']; diff --git a/src/Symfony/Component/Workflow/Tests/WorkflowTest.php b/src/Symfony/Component/Workflow/Tests/WorkflowTest.php index d5f81b55d59c4..2b3d1ec078bac 100644 --- a/src/Symfony/Component/Workflow/Tests/WorkflowTest.php +++ b/src/Symfony/Component/Workflow/Tests/WorkflowTest.php @@ -427,14 +427,16 @@ public function testApplyWithEventDispatcher() $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); } - public static function provideApplyWithEventDispatcherForAnnounceTests() + public static function provideApplyWithEventDispatcherForAnnounceTests(): \Generator { yield [false, [Workflow::DISABLE_ANNOUNCE_EVENT => true]]; yield [true, [Workflow::DISABLE_ANNOUNCE_EVENT => false]]; yield [true, []]; } - /** @dataProvider provideApplyWithEventDispatcherForAnnounceTests */ + /** + * @dataProvider provideApplyWithEventDispatcherForAnnounceTests + */ public function testApplyWithEventDispatcherForAnnounce(bool $fired, array $context) { $definition = $this->createComplexWorkflowDefinition(); diff --git a/src/Symfony/Component/Workflow/Validator/DefinitionValidatorInterface.php b/src/Symfony/Component/Workflow/Validator/DefinitionValidatorInterface.php index f02f582074f1d..c9717b7bebd96 100644 --- a/src/Symfony/Component/Workflow/Validator/DefinitionValidatorInterface.php +++ b/src/Symfony/Component/Workflow/Validator/DefinitionValidatorInterface.php @@ -21,6 +21,8 @@ interface DefinitionValidatorInterface { /** + * @return void + * * @throws InvalidDefinitionException on invalid definition */ public function validate(Definition $definition, string $name); diff --git a/src/Symfony/Component/Workflow/Validator/StateMachineValidator.php b/src/Symfony/Component/Workflow/Validator/StateMachineValidator.php index d4e78cac2754e..20afc8d937b81 100644 --- a/src/Symfony/Component/Workflow/Validator/StateMachineValidator.php +++ b/src/Symfony/Component/Workflow/Validator/StateMachineValidator.php @@ -19,6 +19,9 @@ */ class StateMachineValidator implements DefinitionValidatorInterface { + /** + * @return void + */ public function validate(Definition $definition, string $name) { $transitionFromNames = []; diff --git a/src/Symfony/Component/Workflow/Validator/WorkflowValidator.php b/src/Symfony/Component/Workflow/Validator/WorkflowValidator.php index 6a5c76d95f7fa..c13c2814c3f28 100644 --- a/src/Symfony/Component/Workflow/Validator/WorkflowValidator.php +++ b/src/Symfony/Component/Workflow/Validator/WorkflowValidator.php @@ -27,6 +27,9 @@ public function __construct(bool $singlePlace = false) $this->singlePlace = $singlePlace; } + /** + * @return void + */ public function validate(Definition $definition, string $name) { // Make sure all transitions for one place has unique name. diff --git a/src/Symfony/Component/Yaml/CHANGELOG.md b/src/Symfony/Component/Yaml/CHANGELOG.md index 50852cb1eba8f..0c2021f48b2ef 100644 --- a/src/Symfony/Component/Yaml/CHANGELOG.md +++ b/src/Symfony/Component/Yaml/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add support to dump int keys as strings by using the `Yaml::DUMP_NUMERIC_KEY_AS_STRING` flag + 6.2 --- diff --git a/src/Symfony/Component/Yaml/Command/LintCommand.php b/src/Symfony/Component/Yaml/Command/LintCommand.php index 19a0af08c11f0..95352ac174ff5 100644 --- a/src/Symfony/Component/Yaml/Command/LintCommand.php +++ b/src/Symfony/Component/Yaml/Command/LintCommand.php @@ -50,11 +50,14 @@ public function __construct(string $name = null, callable $directoryIteratorProv $this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...); } + /** + * @return void + */ protected function configure() { $this ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') - ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) ->addOption('exclude', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to exclude') ->addOption('parse-tags', null, InputOption::VALUE_NEGATABLE, 'Parse custom tags', null) ->setHelp(<<format = $input->getOption('format'); $flags = $input->getOption('parse-tags'); - if ('github' === $this->format && !class_exists(GithubActionReporter::class)) { - throw new \InvalidArgumentException('The "github" format is only available since "symfony/console" >= 5.3.'); - } - if (null === $this->format) { // Autodetect format according to CI environment $this->format = class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'; @@ -128,7 +127,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $this->display($io, $filesInfo); } - private function validate(string $content, int $flags, string $file = null) + private function validate(string $content, int $flags, string $file = null): array { $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { if (\E_USER_DEPRECATED === $level) { @@ -155,7 +154,7 @@ private function display(SymfonyStyle $io, array $files): int 'txt' => $this->displayTxt($io, $files), 'json' => $this->displayJson($io, $files), 'github' => $this->displayTxt($io, $files, true), - default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)), + default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; } @@ -240,12 +239,10 @@ private function getParser(): Parser private function getDirectoryIterator(string $directory): iterable { - $default = function ($directory) { - return new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), - \RecursiveIteratorIterator::LEAVES_ONLY - ); - }; + $default = fn ($directory) => new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); if (null !== $this->directoryIteratorProvider) { return ($this->directoryIteratorProvider)($directory, $default); @@ -256,9 +253,7 @@ private function getDirectoryIterator(string $directory): iterable private function isReadable(string $fileOrDirectory): bool { - $default = function ($fileOrDirectory) { - return is_readable($fileOrDirectory); - }; + $default = is_readable(...); if (null !== $this->isReadableProvider) { return ($this->isReadableProvider)($fileOrDirectory, $default); @@ -270,7 +265,12 @@ private function isReadable(string $fileOrDirectory): bool public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues(['txt', 'json', 'github']); + $suggestions->suggestValues($this->getAvailableFormatOptions()); } } + + private function getAvailableFormatOptions(): array + { + return ['txt', 'json', 'github']; + } } diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php index 9249d21d75e52..04646c5cdd337 100644 --- a/src/Symfony/Component/Yaml/Dumper.php +++ b/src/Symfony/Component/Yaml/Dumper.php @@ -66,6 +66,10 @@ public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags $output .= "\n"; } + if (\is_int($key) && Yaml::DUMP_NUMERIC_KEY_AS_STRING & $flags) { + 741A $key = (string) $key; + } + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && str_contains($value, "\n") && !str_contains($value, "\r")) { $blockIndentationIndicator = $this->getBlockIndentationIndicator($value); diff --git a/src/Symfony/Component/Yaml/Exception/ParseException.php b/src/Symfony/Component/Yaml/Exception/ParseException.php index 07c59b9668514..c1a77ad15704b 100644 --- a/src/Symfony/Component/Yaml/Exception/ParseException.php +++ b/src/Symfony/Component/Yaml/Exception/ParseException.php @@ -51,6 +51,8 @@ public function getSnippet(): string /** * Sets the snippet of code near the error. + * + * @return void */ public function setSnippet(string $snippet) { @@ -71,6 +73,8 @@ public function getParsedFile(): string /** * Sets the filename where the error occurred. + * + * @return void */ public function setParsedFile(string $parsedFile) { @@ -89,6 +93,8 @@ public function getParsedLine(): int /** * Sets the line where the error occurred. + * + * @return void */ public function setParsedLine(int $parsedLine) { @@ -97,7 +103,7 @@ public function setParsedLine(int $parsedLine) $this->updateRepr(); } - private function updateRepr() + private function updateRepr(): void { $this->message = $this->rawMessage; diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index 127e50e84a506..c2a93bab6c5da 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -34,7 +34,7 @@ class Inline private static bool $objectForMap = false; private static bool $constantSupport = false; - public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null) + public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null): void { self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); @@ -110,7 +110,11 @@ public static function dump(mixed $value, int $flags = 0): string return self::dumpNull($flags); case $value instanceof \DateTimeInterface: - return $value->format('c'); + return $value->format(match (true) { + !$length = \strlen(rtrim($value->format('u'), '0')) => 'c', + $length < 4 => 'Y-m-d\TH:i:s.vP', + default => 'Y-m-d\TH:i:s.uP', + }); case $value instanceof \UnitEnum: return sprintf('!php/const %s::%s', $value::class, $value->name); case \is_object($value): @@ -239,6 +243,10 @@ private static function dumpHashArray(array|\ArrayObject|\stdClass $value, int $ { $output = []; foreach ($value as $key => $val) { + if (\is_int($key) && Yaml::DUMP_NUMERIC_KEY_AS_STRING & $flags) { + $key = (string) $key; + } + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); } @@ -708,6 +716,10 @@ private static function evaluateScalar(string $scalar, int $flags, array &$refer return $time; } + if ('' !== rtrim($time->format('u'), '0')) { + return (float) $time->format('U.u'); + } + try { if (false !== $scalar = $time->getTimestamp()) { return $scalar; diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 32eb429f5a686..ddfbcfd83a06f 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -99,7 +99,7 @@ public function parse(string $value, int $flags = 0): mixed return $data; } - private function doParse(string $value, int $flags) + private function doParse(string $value, int $flags): mixed { $this->currentLineNb = -1; $this->currentLine = ''; @@ -503,7 +503,7 @@ private function doParse(string $value, int $flags) return empty($data) ? null : $data; } - private function parseBlock(int $offset, string $yaml, int $flags) + private function parseBlock(int $offset, string $yaml, int $flags): mixed { $skippedLineNumbers = $this->skippedLineNumbers; @@ -844,8 +844,8 @@ private function parseBlockScalar(string $style, string $chomping = '', int $ind while ( $notEOF && ( - $isCurrentLineBlank || - self::preg_match($pattern, $this->currentLine, $matches) + $isCurrentLineBlank + || self::preg_match($pattern, $this->currentLine, $matches) ) ) { if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { diff --git a/src/Symfony/Component/Yaml/Tag/TaggedValue.php b/src/Symfony/Component/Yaml/Tag/TaggedValue.php index c7946c27ec4d0..3e09b93ee021f 100644 --- a/src/Symfony/Component/Yaml/Tag/TaggedValue.php +++ b/src/Symfony/Component/Yaml/Tag/TaggedValue.php @@ -31,7 +31,7 @@ public function getTag(): string return $this->tag; } - public function getValue() + public function getValue(): mixed { return $this->value; } diff --git a/src/Symfony/Component/Yaml/Tests/Command/LintCommandTest.php b/src/Symfony/Component/Yaml/Tests/Command/LintCommandTest.php index 3b92c43365470..8016395875c26 100644 --- a/src/Symfony/Component/Yaml/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Component/Yaml/Tests/Command/LintCommandTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Application; -use Symfony\Component\Console\CI\GithubActionReporter; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\CommandCompletionTester; @@ -68,11 +67,6 @@ public function testLintIncorrectFile() public function testLintIncorrectFileWithGithubFormat() { - if (!class_exists(GithubActionReporter::class)) { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The "github" format is only available since "symfony/console" >= 5.3.'); - } - $incorrectContent = <<execute(['filename' => $filename, '--format' => 'github'], ['decorated' => false]); - if (!class_exists(GithubActionReporter::class)) { - return; - } - self::assertEquals(1, $tester->getStatusCode(), 'Returns 1 in case of error'); self::assertStringMatchesFormat('%A::error file=%s,line=2,col=0::Unable to parse at line 2 (near "bar")%A', trim($tester->getDisplay())); } diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index 87dc014e02c0f..24758b810445b 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -853,6 +853,96 @@ public function testDumpNullAsTilde() $this->assertSame('{ foo: ~ }', $this->dumper->dump(['foo' => null], 0, 0, Yaml::DUMP_NULL_AS_TILDE)); } + /** + * @dataProvider getNumericKeyData + */ + public function testDumpInlineNumericKeyAsString(array $input, bool $inline, int $flags, string $expected) + { + $this->assertSame($expected, $this->dumper->dump($input, $inline ? 0 : 4, 0, $flags)); + } + + public static function getNumericKeyData() + { + yield 'Int key with flag inline' => [ + [200 => 'foo'], + true, + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '200': foo }", + ]; + + yield 'Int key without flag inline' => [ + [200 => 'foo'], + true, + 0, + '{ 200: foo }', + ]; + + $expected = <<<'YAML' + '200': foo + + YAML; + + yield 'Int key with flag' => [ + [200 => 'foo'], + false, + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + $expected, + ]; + + $expected = <<<'YAML' + 200: foo + + YAML; + + yield 'Int key without flag' => [ + [200 => 'foo'], + false, + 0, + $expected, + ]; + + $expected = <<<'YAML' + - 200 + - foo + + YAML; + + yield 'List array with flag' => [ + [200, 'foo'], + false, + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + $expected, + ]; + + $expected = <<<'YAML' + '200': !number 5 + + YAML; + + yield 'Int tagged value with flag' => [ + [ + 200 => new TaggedValue('number', 5), + ], + false, + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + $expected, + ]; + + $expected = <<<'YAML' + 200: !number 5 + + YAML; + + yield 'Int tagged value without flag' => [ + [ + 200 => new TaggedValue('number', 5), + ], + false, + 0, + $expected, + ]; + } + public function testDumpIdeographicSpaces() { $expected = <<assertSame($expected, rtrim($this->dumper->dump($input, 1))); + } + + public static function getDateTimeData() + { + yield 'Date without subsecond precision' => [ + ['date' => new \DateTimeImmutable('2023-01-24T01:02:03Z')], + 'date: 2023-01-24T01:02:03+00:00', + ]; + + yield 'Date with one digit for milliseconds' => [ + ['date' => new \DateTimeImmutable('2023-01-24T01:02:03.4Z')], + 'date: 2023-01-24T01:02:03.400+00:00', + ]; + + yield 'Date with two digits for milliseconds' => [ + ['date' => new \DateTimeImmutable('2023-01-24T01:02:03.45Z')], + 'date: 2023-01-24T01:02:03.450+00:00', + ]; + + yield 'Date with full milliseconds' => [ + ['date' => new \DateTimeImmutable('2023-01-24T01:02:03.456Z')], + 'date: 2023-01-24T01:02:03.456+00:00', + ]; + + yield 'Date with four digits for microseconds' => [ + ['date' => new \DateTimeImmutable('2023-01-24T01:02:03.4567Z')], + 'date: 2023-01-24T01:02:03.456700+00:00', + ]; + + yield 'Date with five digits for microseconds' => [ + ['date' => new \DateTimeImmutable('2023-01-24T01:02:03.45678Z')], + 'date: 2023-01-24T01:02:03.456780+00:00', + ]; + + yield 'Date with full microseconds' => [ + ['date' => new \DateTimeImmutable('2023-01-24T01:02:03.456789Z')], + 'date: 2023-01-24T01:02:03.456789+00:00', + ]; + } + private function assertSameData($expected, $actual) { $this->assertEquals($expected, $actual); diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index 5d8d1405b6238..2f9f58350f0a0 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -563,9 +563,10 @@ public static function getTestsForDump() /** * @dataProvider getTimestampTests */ - public function testParseTimestampAsUnixTimestampByDefault(string $yaml, int $year, int $month, int $day, int $hour, int $minute, int $second) + public function testParseTimestampAsUnixTimestampByDefault(string $yaml, int $year, int $month, int $day, int $hour, int $minute, int $second, int $microsecond) { - $this->assertSame(gmmktime($hour, $minute, $second, $month, $day, $year), Inline::parse($yaml)); + $expectedDate = (new \DateTimeImmutable($yaml, new \DateTimeZone('UTC')))->format('U'); + $this->assertSame($microsecond ? (float) "$expectedDate.$microsecond" : (int) $expectedDate, Inline::parse($yaml)); } /** @@ -617,6 +618,98 @@ public function testDumpDateTime($dateTime, $expected) $this->assertSame($expected, Inline::dump($dateTime)); } + /** + * @dataProvider getNumericKeyData + */ + public function testDumpNumericKeyAsString(array|int $input, int $flags, string $expected) + { + $this->assertSame($expected, Inline::dump($input, $flags)); + } + + public static function getNumericKeyData() + { + yield 'Int with flag' => [ + 200, + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + '200', + ]; + + yield 'Int key with flag' => [ + [200 => 'foo'], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '200': foo }", + ]; + + yield 'Int value with flag' => [ + [200 => 200], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '200': 200 }", + ]; + + yield 'String key with flag' => [ + ['200' => 'foo'], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '200': foo }", + ]; + + yield 'Mixed with flag' => [ + [42 => 'a', 'b' => 'c', 'd' => 43], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '42': a, b: c, d: 43 }", + ]; + + yield 'Auto-index with flag' => [ + ['a', 'b', 42], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + '[a, b, 42]', + ]; + + yield 'Complex mixed array with flag' => [ + [ + 42 => [ + 'foo' => 43, + 44 => 'bar', + ], + 45 => 'baz', + 46, + ], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + "{ '42': { foo: 43, '44': bar }, '45': baz, '46': 46 }", + ]; + + yield 'Int tagged value with flag' => [ + [ + 'count' => new TaggedValue('number', 5), + ], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + '{ count: !number 5 }', + ]; + + yield 'Array tagged value with flag' => [ + [ + 'user' => new TaggedValue('metadata', [ + 'john', + 42, + ]), + ], + Yaml::DUMP_NUMERIC_KEY_AS_STRING, + '{ user: !metadata [john, 42] }', + ]; + + $arrayObject = new \ArrayObject(); + $arrayObject['foo'] = 'bar'; + $arrayObject[42] = 'baz'; + $arrayObject['baz'] = 43; + + yield 'Object value with flag' => [ + [ + 'user' => $arrayObject, + ], + Yaml::DUMP_NUMERIC_KEY_AS_STRING | Yaml::DUMP_OBJECT_AS_MAP, + "{ user: { foo: bar, '42': baz, baz: 43 } }", + ]; + } + public function testDumpUnitEnum() { $this->assertSame("!php/const Symfony\Component\Yaml\Tests\Fixtures\FooUnitEnum::BAR", Inline::dump(FooUnitEnum::BAR)); diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 8f89eca4e6566..2918d1b07c337 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -1541,6 +1541,15 @@ public static function getInvalidBinaryData() ]; } + public function testParseDateWithSubseconds() + { + $yaml = <<<'EOT' +date: 2002-12-14T01:23:45.670000Z +EOT; + + $this->assertSameData(['date' => 1039829025.67], $this->parser->parse($yaml)); + } + public function testParseDateAsMappingValue() { $yaml = <<<'EOT' diff --git a/src/Symfony/Component/Yaml/Unescaper.php b/src/Symfony/Component/Yaml/Unescaper.php index 2238210d93dfe..9e640ff248ff5 100644 --- a/src/Symfony/Component/Yaml/Unescaper.php +++ b/src/Symfony/Component/Yaml/Unescaper.php @@ -45,9 +45,7 @@ public function unescapeSingleQuotedString(string $value): string */ public function unescapeDoubleQuotedString(string $value): string { - $callback = function ($match) { - return $this->unescapeCharacter($match[0]); - }; + $callback = fn ($match) => $this->unescapeCharacter($match[0]); // evaluate the string return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); diff --git a/src/Symfony/Component/Yaml/Yaml.php b/src/Symfony/Component/Yaml/Yaml.php index 49784216e96e9..e2d2af731083d 100644 --- a/src/Symfony/Component/Yaml/Yaml.php +++ b/src/Symfony/Component/Yaml/Yaml.php @@ -34,6 +34,7 @@ class Yaml public const PARSE_CUSTOM_TAGS = 512; public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; public const DUMP_NULL_AS_TILDE = 2048; + public const DUMP_NUMERIC_KEY_AS_STRING = 4096; /** * Parses a YAML file into a PHP value. diff --git a/src/Symfony/Component/Yaml/composer.json b/src/Symfony/Component/Yaml/composer.json index 839314bf51395..019776cef9dba 100644 --- a/src/Symfony/Component/Yaml/composer.json +++ b/src/Symfony/Component/Yaml/composer.json @@ -25,9 +25,6 @@ "conflict": { "symfony/console": "<5.4" }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, "autoload": { "psr-4": { "Symfony\\Component\\Yaml\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Contracts/Cache/CacheInterface.php b/src/Symfony/Contracts/Cache/CacheInterface.php index 0d4f4030b5db9..a4fcea731e596 100644 --- a/src/Symfony/Contracts/Cache/CacheInterface.php +++ b/src/Symfony/Contracts/Cache/CacheInterface.php @@ -29,14 +29,18 @@ interface CacheInterface * requested key, that could be used e.g. for expiration control. It could also * be an ItemInterface instance when its additional features are needed. * - * @param string $key The key of the item to retrieve from the cache - * @param callable|CallbackInterface $callback Should return the computed value for the given key/item - * @param float|null $beta A float that, as it grows, controls the likeliness of triggering - * early expiration. 0 disables it, INF forces immediate expiration. - * The default (or providing null) is implementation dependent but should - * typically be 1.0, which should provide optimal stampede protection. - * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration - * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} + * @template T + * + * @param string $key The key of the item to retrieve from the cache + * @param (callable(CacheItemInterface,bool):T)|(callable(ItemInterface,bool):T)|CallbackInterface $callback + * @param float|null $beta A float that, as it grows, controls the likeliness of triggering + * early expiration. 0 disables it, INF forces immediate expiration. + * The default (or providing null) is implementation dependent but should + * typically be 1.0, which should provide optimal stampede protection. + * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration + * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} + * + * @return T * * @throws InvalidArgumentException When $key is not valid or when $beta is negative */ diff --git a/src/Symfony/Contracts/Cache/CallbackInterface.php b/src/Symfony/Contracts/Cache/CallbackInterface.php index 437a3c9396616..15941e9c9d7d3 100644 --- a/src/Symfony/Contracts/Cache/CallbackInterface.php +++ b/src/Symfony/Contracts/Cache/CallbackInterface.php @@ -17,6 +17,8 @@ * Computes and returns the cached value of an item. * * @author Nicolas Grekas + * + * @template T */ interface CallbackInterface { @@ -24,7 +26,7 @@ interface CallbackInterface * @param CacheItemInterface|ItemInterface $item The item to compute the value for * @param bool &$save Should be set to false when the value should not be saved in the pool * - * @return mixed The computed value for the passed item + * @return T The computed value for the passed item */ public function __invoke(CacheItemInterface $item, bool &$save): mixed; } diff --git a/src/Symfony/Contracts/Cache/README.md b/src/Symfony/Contracts/Cache/README.md index 7085a6996e4d7..ffe0833afa360 100644 --- a/src/Symfony/Contracts/Cache/README.md +++ b/src/Symfony/Contracts/Cache/README.md @@ -3,7 +3,7 @@ Symfony Cache Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful - and +Can be used to build on semantics that the Symfony components proved useful and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/src/Symfony/Contracts/Cache/composer.json b/src/Symfony/Contracts/Cache/composer.json index 27b2c84f9d912..79a1d2dd1416e 100644 --- a/src/Symfony/Contracts/Cache/composer.json +++ b/src/Symfony/Contracts/Cache/composer.json @@ -19,16 +19,13 @@ "php": ">=8.1", "psr/cache": "^3.0" }, - "suggest": { - "symfony/cache-implementation": "" - }, "autoload": { "psr-4": { "Symfony\\Contracts\\Cache\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Deprecation/README.md b/src/Symfony/Contracts/Deprecation/README.md index 4957933a6cc50..9814864c03f58 100644 --- a/src/Symfony/Contracts/Deprecation/README.md +++ b/src/Symfony/Contracts/Deprecation/README.md @@ -22,5 +22,5 @@ trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use This will generate the following message: `Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` -While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +While not recommended, the deprecation notices can be completely ignored by declaring an empty `function trigger_deprecation() {}` in your application. diff --git a/src/Symfony/Contracts/Deprecation/composer.json b/src/Symfony/Contracts/Deprecation/composer.json index be6c494b149a6..774200fdcdae8 100644 --- a/src/Symfony/Contracts/Deprecation/composer.json +++ b/src/Symfony/Contracts/Deprecation/composer.json @@ -25,7 +25,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/EventDispatcher/README.md b/src/Symfony/Contracts/EventDispatcher/README.md index b1ab4c00ceae2..332b961cb8810 100644 --- a/src/Symfony/Contracts/EventDispatcher/README.md +++ b/src/Symfony/Contracts/EventDispatcher/README.md @@ -3,7 +3,7 @@ Symfony EventDispatcher Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful - and +Can be used to build on semantics that the Symfony components proved useful and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/src/Symfony/Contracts/EventDispatcher/composer.json b/src/Symfony/Contracts/EventDispatcher/composer.json index eecac79ca57c2..4b6a136add482 100644 --- a/src/Symfony/Contracts/EventDispatcher/composer.json +++ b/src/Symfony/Contracts/EventDispatcher/composer.json @@ -19,16 +19,13 @@ "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "autoload": { "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/HttpClient/README.md b/src/Symfony/Contracts/HttpClient/README.md index 03b3a69b70208..24d72f566a879 100644 --- a/src/Symfony/Contracts/HttpClient/README.md +++ b/src/Symfony/Contracts/HttpClient/README.md @@ -3,7 +3,7 @@ Symfony HttpClient Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful - and +Can be used to build on semantics that the Symfony components proved useful and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/src/Symfony/Contracts/HttpClient/composer.json b/src/Symfony/Contracts/HttpClient/composer.json index d4176ccef72ce..0c2102fd3cff6 100644 --- a/src/Symfony/Contracts/HttpClient/composer.json +++ b/src/Symfony/Contracts/HttpClient/composer.json @@ -18,9 +18,6 @@ "require": { "php": ">=8.1" }, - "suggest": { - "symfony/http-client-implementation": "" - }, "autoload": { "psr-4": { "Symfony\\Contracts\\HttpClient\\": "" }, "exclude-from-classmap": [ @@ -30,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Service/README.md b/src/Symfony/Contracts/Service/README.md index 41e054a101cf4..42841a57d0908 100644 --- a/src/Symfony/Contracts/Service/README.md +++ b/src/Symfony/Contracts/Service/README.md @@ -3,7 +3,7 @@ Symfony Service Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful - and +Can be used to build on semantics that the Symfony components proved useful and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/src/Symfony/Contracts/Service/ResetInterface.php b/src/Symfony/Contracts/Service/ResetInterface.php index 1af1075eeeca7..a4f389b01f80d 100644 --- a/src/Symfony/Contracts/Service/ResetInterface.php +++ b/src/Symfony/Contracts/Service/ResetInterface.php @@ -26,5 +26,8 @@ */ interface ResetInterface { + /** + * @return void + */ public function reset(); } diff --git a/src/Symfony/Contracts/Service/ServiceProviderInterface.php b/src/Symfony/Contracts/Service/ServiceProviderInterface.php index a28fd82ea49a4..c05e4bfe7bc35 100644 --- a/src/Symfony/Contracts/Service/ServiceProviderInterface.php +++ b/src/Symfony/Contracts/Service/ServiceProviderInterface.php @@ -19,7 +19,7 @@ * @author Nicolas Grekas * @author Mateusz Sip * - * @template T of mixed + * @template-covariant T of mixed */ interface ServiceProviderInterface extends ContainerInterface { diff --git a/src/Symfony/Contracts/Service/composer.json b/src/Symfony/Contracts/Service/composer.json index af3559ed4983d..a2530e9aa9095 100644 --- a/src/Symfony/Contracts/Service/composer.json +++ b/src/Symfony/Contracts/Service/composer.json @@ -22,9 +22,6 @@ "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" }, "exclude-from-classmap": [ @@ -34,7 +31,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Tests/Service/ServiceSubscriberTraitTest.php b/src/Symfony/Contracts/Tests/Service/ServiceSubscriberTraitTest.php index e8ee7bb3d1a56..3eb4b31940021 100644 --- a/src/Symfony/Contracts/Tests/Service/ServiceSubscriberTraitTest.php +++ b/src/Symfony/Contracts/Tests/Service/ServiceSubscriberTraitTest.php @@ -88,7 +88,7 @@ public function aParentService(): Service1 { } - public function setContainer(ContainerInterface $container) + public function setContainer(ContainerInterface $container): ?ContainerInterface { r D74 eturn $container; } diff --git a/src/Symfony/Contracts/Translation/LocaleAwareInterface.php b/src/Symfony/Contracts/Translation/LocaleAwareInterface.php index 6923b977e453c..db40ba13e05bf 100644 --- a/src/Symfony/Contracts/Translation/LocaleAwareInterface.php +++ b/src/Symfony/Contracts/Translation/LocaleAwareInterface.php @@ -16,6 +16,8 @@ interface LocaleAwareInterface /** * Sets the current locale. * + * @return void + * * @throws \InvalidArgumentException If the locale contains invalid characters */ public function setLocale(string $locale); diff --git a/src/Symfony/Contracts/Translation/README.md b/src/Symfony/Contracts/Translation/README.md index 42e5c51754ed6..b211d5849c818 100644 --- a/src/Symfony/Contracts/Translation/README.md +++ b/src/Symfony/Contracts/Translation/README.md @@ -3,7 +3,7 @@ Symfony Translation Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful - and +Can be used to build on semantics that the Symfony components proved useful and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/src/Symfony/Contracts/Translation/TranslatorTrait.php b/src/Symfony/Contracts/Translation/TranslatorTrait.php index 46e9170742059..e3b0adff05980 100644 --- a/src/Symfony/Contracts/Translation/TranslatorTrait.php +++ b/src/Symfony/Contracts/Translation/TranslatorTrait.php @@ -22,6 +22,9 @@ trait TranslatorTrait { private ?string $locale = null; + /** + * @return void + */ public function setLocale(string $locale) { $this->locale = $locale; diff --git a/src/Symfony/Contracts/Translation/composer.json b/src/Symfony/Contracts/Translation/composer.json index 15e4bc1b6441f..49e6dc7ffc919 100644 --- a/src/Symfony/Contracts/Translation/composer.json +++ b/src/Symfony/Contracts/Translation/composer.json @@ -18,9 +18,6 @@ "require": { "php": ">=8.1" }, - "suggest": { - "symfony/translation-implementation": "" - }, "autoload": { "psr-4": { "Symfony\\Contracts\\Translation\\": "" }, "exclude-from-classmap": [ @@ -30,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/composer.json b/src/Symfony/Contracts/composer.json index 63bba367386d7..a165e44844c71 100644 --- a/src/Symfony/Contracts/composer.json +++ b/src/Symfony/Contracts/composer.json @@ -35,13 +35,6 @@ "symfony/service-contracts": "self.version", "symfony/translation-contracts": "self.version" }, - "suggest": { - "symfony/cache-implementation": "", - "symfony/event-dispatcher-implementation": "", - "symfony/http-client-implementation": "", - "symfony/service-implementation": "", - "symfony/translation-implementation": "" - }, "autoload": { "psr-4": { "Symfony\\Contracts\\": "" }, "files": [ "Deprecation/function.php" ], @@ -52,7 +45,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "3.3-dev" } } } 0
    - - {{ helper.render_log_message('debug', loop.index, log) }} + {{ _self.render_log_message('debug', loop.index, log) }}
    {{ helper.render_data_cell(item, loop.index, cellPrefix) }}{{ helper.render_context_cell(item, loop.index, cellPrefix) }}{{ helper.render_normalizer_cell(item, loop.index, cellPrefix) }}{{ helper.render_encoder_cell(item, loop.index, cellPrefix) }}{{ helper.render_time_cell(item) }}{{ helper.render_caller_cell(item, loop.index, cellPrefix) }}{{ _self.render_data_cell(item, loop.index, cellPrefix) }}{{ _self.render_context_cell(item, loop.index, cellPrefix) }}{{ _self.render_normalizer_cell(item, loop.index, cellPrefix) }}{{ _self.render_encoder_cell(item, loop.index, cellPrefix) }}{{ _self.render_time_cell(item) }}{{ _self.render_caller_cell(item, loop.index, cellPrefix) }}
    {{ helper.render_data_cell(item, loop.index, cellPrefix) }}{{ helper.render_context_cell(item, loop.index, cellPrefix) }}{{ helper.render_normalizer_cell(item, loop.index, cellPrefix) }}{{ helper.render_time_cell(item) }}{{ helper.render_caller_cell(item, loop.index, cellPrefix) }}{{ _self.render_data_cell(item, loop.index, cellPrefix) }}{{ _self.render_context_cell(item, loop.index, cellPrefix) }}{{ _self.render_normalizer_cell(item, loop.index, cellPrefix) }}{{ _self.render_time_cell(item) }}{{ _self.render_caller_cell(item, loop.index, cellPrefix) }}
    {{ helper.render_data_cell(item, loop.index, cellPrefix) }}{{ helper.render_context_cell(item, loop.index, cellPrefix) }}{{ helper.render_encoder_cell(item, loop.index, cellPrefix) }}{{ helper.render_time_cell(item) }}{{ helper.render_caller_cell(item, loop.index, cellPrefix) }}{{ _self.render_data_cell(item, loop.index, cellPrefix) }}{{ _self.render_context_cell(item, loop.index, cellPrefix) }}{{ _self.render_encoder_cell(item, loop.index, cellPrefix) }}{{ _self.render_time_cell(item) }}{{ _self.render_caller_cell(item, loop.index, cellPrefix) }}
    - {{ result.ip }} {{ helper.profile_search_filter(request, result, 'ip') }} + {{ result.ip }} {{ _self.profile_search_filter(request, result, 'ip') }} - {{ result.method }} {{ helper.profile_search_filter(request, result, 'method') }} + {{ result.method }} {{ _self.profile_search_filter(request, result, 'method') }} {{ result.url }} - {{ helper.profile_search_filter(request, result, 'url') }} + {{ _self.profile_search_filter(request, result, 'url') }} - {{ result.time|date('d-M-Y') }} - {{ result.time|date('H:i:s') }} + + {{ result.token }}