diff --git a/CHANGELOG.md b/CHANGELOG.md index ce62c9cdf..76b3cb947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +7.4 +--- + + * Add `ControllerHelper`; the helpers from AbstractController as a standalone service + * Allow using their name without added suffix when using `#[Target]` for custom services + * Deprecate `Symfony\Bundle\FrameworkBundle\Console\Application::add()` in favor of `Symfony\Bundle\FrameworkBundle\Console\Application::addCommand()` + * Add `assertEmailAddressNotContains()` to the `MailerAssertionsTrait` + 7.3 --- @@ -698,7 +706,7 @@ CHANGELOG * added Client::enableProfiler() * a new parameter has been added to the DIC: `router.request_context.base_url` You can customize it for your functional tests or for generating URLs with - the right base URL when your are in the CLI context. + the right base URL when you are in the CLI context. * added support for default templates per render tag 2.1.0 diff --git a/Command/AboutCommand.php b/Command/AboutCommand.php index 0c6899328..8b4604970 100644 --- a/Command/AboutCommand.php +++ b/Command/AboutCommand.php @@ -36,11 +36,11 @@ protected function configure(): void { $this ->setHelp(<<<'EOT' -The %command.name% command displays information about the current Symfony project. + The %command.name% command displays information about the current Symfony project. -The PHP section displays important configuration that could affect your application. The values might -be different between web and CLI. -EOT + The PHP section displays important configuration that could affect your application. The values might + be different between web and CLI. + EOT ) ; } diff --git a/Command/AssetsInstallCommand.php b/Command/AssetsInstallCommand.php index 5dc8c828e..099204cae 100644 --- a/Command/AssetsInstallCommand.php +++ b/Command/AssetsInstallCommand.php @@ -23,7 +23,6 @@ use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; -use Symfony\Component\HttpKernel\Bundle\BundleInterface; use Symfony\Component\HttpKernel\KernelInterface; /** @@ -58,24 +57,24 @@ protected function configure(): void ->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks') ->addOption('no-cleanup', null, InputOption::VALUE_NONE, 'Do not remove the assets of the bundles that no longer exist') ->setHelp(<<<'EOT' -The %command.name% command installs bundle assets into a given -directory (e.g. the public directory). + The %command.name% command installs bundle assets into a given + directory (e.g. the public directory). - php %command.full_name% public + php %command.full_name% public -A "bundles" directory will be created inside the target directory and the -"Resources/public" directory of each bundle will be copied into it. + A "bundles" directory will be created inside the target directory and the + "Resources/public" directory of each bundle will be copied into it. -To create a symlink to each bundle instead of copying its assets, use the ---symlink option (will fall back to hard copies when symbolic links aren't possible: + To create a symlink to each bundle instead of copying its assets, use the + --symlink option (will fall back to hard copies when symbolic links aren't possible: - php %command.full_name% public --symlink + php %command.full_name% public --symlink -To make symlink relative, add the --relative option: + To make symlink relative, add the --relative option: - php %command.full_name% public --symlink --relative + php %command.full_name% public --symlink --relative -EOT + EOT ) ; } @@ -119,7 +118,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $copyUsed = false; $exitCode = 0; $validAssetDirs = []; - /** @var BundleInterface $bundle */ foreach ($kernel->getBundles() as $bundle) { if (!is_dir($originDir = $bundle->getPath().'/Resources/public') && !is_dir($originDir = $bundle->getPath().'/public')) { continue; @@ -239,7 +237,7 @@ private function symlink(string $originDir, string $targetDir, bool $relative = */ private function hardCopy(string $originDir, string $targetDir): string { - $this->filesystem->mkdir($targetDir, 0777); + $this->filesystem->mkdir($targetDir, 0o777); // We use a custom iterator to ignore VCS files $this->filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir)); diff --git a/Command/BuildDebugContainerTrait.php b/Command/BuildDebugContainerTrait.php index 2f625e9e3..011510095 100644 --- a/Command/BuildDebugContainerTrait.php +++ b/Command/BuildDebugContainerTrait.php @@ -39,7 +39,9 @@ protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilde return $this->container; } - if (!$kernel->isDebug() || !$kernel->getContainer()->getParameter('debug.container.dump') || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) { + $file = $kernel->isDebug() ? $kernel->getContainer()->getParameter('debug.container.dump') : false; + + if (!$file || !(new ConfigCache($file, true))->isFresh()) { $buildContainer = \Closure::bind(function () { $this->initializeBundles(); @@ -57,13 +59,17 @@ protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilde return $containerBuilder; }, $kernel, $kernel::class); $container = $buildContainer(); - (new XmlFileLoader($container, new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); - $locatorPass = new ServiceLocatorTagPass(); - $locatorPass->process($container); - $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); - $container->getCompilerPassConfig()->setOptimizationPasses([]); - $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); + if (str_ends_with($file, '.xml') && is_file(substr_replace($file, '.ser', -4))) { + $dumpedContainer = unserialize(file_get_contents(substr_replace($file, '.ser', -4))); + $container->setDefinitions($dumpedContainer->getDefinitions()); + $container->setAliases($dumpedContainer->getAliases()); + $container->__construct($dumpedContainer->getParameterBag()); + } else { + (new XmlFileLoader($container, new FileLocator()))->load($file); + $locatorPass = new ServiceLocatorTagPass(); + $locatorPass->process($container); + } } return $this->container = $container; diff --git a/Command/CacheClearCommand.php b/Command/CacheClearCommand.php index 0e48ead59..3f3960ef8 100644 --- a/Command/CacheClearCommand.php +++ b/Command/CacheClearCommand.php @@ -56,12 +56,12 @@ protected function configure(): void new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'), ]) ->setHelp(<<<'EOF' -The %command.name% command clears and warms up the application cache for a given environment -and debug mode: + The %command.name% command clears and warms up the application cache for a given environment + and debug mode: - php %command.full_name% --env=dev - php %command.full_name% --env=prod --no-debug -EOF + php %command.full_name% --env=dev + php %command.full_name% --env=prod --no-debug + EOF ) ; } @@ -213,7 +213,7 @@ private function isNfs(string $dir): bool if ('/' === \DIRECTORY_SEPARATOR && @is_readable('/proc/mounts') && $files = @file('/proc/mounts')) { foreach ($files as $mount) { $mount = \array_slice(explode(' ', $mount), 1, -3); - if (!\in_array(array_pop($mount), ['vboxsf', 'nfs'])) { + if (!\in_array(array_pop($mount), ['vboxsf', 'nfs'], true)) { continue; } $mounts[] = implode(' ', $mount).'/'; diff --git a/Command/CachePoolClearCommand.php b/Command/CachePoolClearCommand.php index 5d840e597..d4bca0d8f 100644 --- a/Command/CachePoolClearCommand.php +++ b/Command/CachePoolClearCommand.php @@ -51,10 +51,10 @@ protected function configure(): void ->addOption('all', null, InputOption::VALUE_NONE, 'Clear all cache pools') ->addOption('exclude', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'A list of cache pools or cache pool clearers to exclude') ->setHelp(<<<'EOF' -The %command.name% command clears the given cache pools or cache pool clearers. + The %command.name% command clears the given cache pools or cache pool clearers. - %command.full_name% [...] -EOF + %command.full_name% [...] + EOF ) ; } diff --git a/Command/CachePoolDeleteCommand.php b/Command/CachePoolDeleteCommand.php index 8fb1d1aaa..c3c23a391 100644 --- a/Command/CachePoolDeleteCommand.php +++ b/Command/CachePoolDeleteCommand.php @@ -47,10 +47,10 @@ protected function configure(): void new InputArgument('key', InputArgument::REQUIRED, 'The cache key to delete from the pool'), ]) ->setHelp(<<<'EOF' -The %command.name% deletes an item from a given cache pool. + The %command.name% deletes an item from a given cache pool. - %command.full_name% -EOF + %command.full_name% + EOF ) ; } diff --git a/Command/CachePoolListCommand.php b/Command/CachePoolListCommand.php index 6b8e71eb0..6aedfb0c0 100644 --- a/Command/CachePoolListCommand.php +++ b/Command/CachePoolListCommand.php @@ -38,8 +38,8 @@ protected function configure(): void { $this ->setHelp(<<<'EOF' -The %command.name% command lists all available cache pools. -EOF + The %command.name% command lists all available cache pools. + EOF ) ; } diff --git a/Command/CachePoolPruneCommand.php b/Command/CachePoolPruneCommand.php index 745a001cc..5036da8ef 100644 --- a/Command/CachePoolPruneCommand.php +++ b/Command/CachePoolPruneCommand.php @@ -39,10 +39,10 @@ protected function configure(): void { $this ->setHelp(<<<'EOF' -The %command.name% command deletes all expired items from all pruneable pools. + The %command.name% command deletes all expired items from all pruneable pools. - %command.full_name% -EOF + %command.full_name% + EOF ) ; } diff --git a/Command/CacheWarmupCommand.php b/Command/CacheWarmupCommand.php index b096b0801..8ade22560 100644 --- a/Command/CacheWarmupCommand.php +++ b/Command/CacheWarmupCommand.php @@ -44,11 +44,11 @@ protected function configure(): void new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'), ]) ->setHelp(<<<'EOF' -The %command.name% command warms up the cache. + The %command.name% command warms up the cache. -Before running this command, the cache must be empty. + Before running this command, the cache must be empty. -EOF + EOF ) ; } diff --git a/Command/ConfigDebugCommand.php b/Command/ConfigDebugCommand.php index 8d5f85cee..50c8dddf5 100644 --- a/Command/ConfigDebugCommand.php +++ b/Command/ConfigDebugCommand.php @@ -49,23 +49,23 @@ protected function configure(): void new InputOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), class_exists(Yaml::class) ? 'txt' : 'json'), ]) ->setHelp(<<%command.name% command dumps the current configuration for an -extension/bundle. + The %command.name% command dumps the current configuration for an + extension/bundle. -Either the extension alias or bundle name can be used: + Either the extension alias or bundle name can be used: - php %command.full_name% framework - php %command.full_name% FrameworkBundle + php %command.full_name% framework + php %command.full_name% FrameworkBundle -The --format option specifies the format of the command output: + The --format option specifies the format of the command output: - php %command.full_name% framework --format=json + php %command.full_name% framework --format=json -For dumping a specific option, add its path as second argument: + For dumping a specific option, add its path as second argument: - php %command.full_name% framework serializer.enabled + php %command.full_name% framework serializer.enabled -EOF + EOF ) ; } diff --git a/Command/ConfigDumpReferenceCommand.php b/Command/ConfigDumpReferenceCommand.php index 3cb744d74..3a6d1252d 100644 --- a/Command/ConfigDumpReferenceCommand.php +++ b/Command/ConfigDumpReferenceCommand.php @@ -47,23 +47,23 @@ protected function configure(): void new InputOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'yaml'), ]) ->setHelp(<<%command.name% command dumps the default configuration for an -extension/bundle. + The %command.name% command dumps the default configuration for an + extension/bundle. -Either the extension alias or bundle name can be used: + Either the extension alias or bundle name can be used: - php %command.full_name% framework - php %command.full_name% FrameworkBundle + php %command.full_name% framework + php %command.full_name% FrameworkBundle -The --format option specifies the format of the command output: + The --format option specifies the format of the command output: - php %command.full_name% FrameworkBundle --format=json + php %command.full_name% FrameworkBundle --format=json -For dumping a specific option, add its path as second argument (only available for the yaml format): + For dumping a specific option, add its path as second argument (only available for the yaml format): - php %command.full_name% framework http_client.default_options + php %command.full_name% framework http_client.default_options -EOF + EOF ) ; } diff --git a/Command/ContainerDebugCommand.php b/Command/ContainerDebugCommand.php index 17c71bdca..f0f7d5a1b 100644 --- a/Command/ContainerDebugCommand.php +++ b/Command/ContainerDebugCommand.php @@ -57,59 +57,59 @@ protected function configure(): void new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Display deprecations generated when compiling and warming up the container'), ]) ->setHelp(<<<'EOF' -The %command.name% command displays all configured public services: + The %command.name% command displays all configured public services: - php %command.full_name% + php %command.full_name% -To see deprecations generated during container compilation and cache warmup, use the --deprecations option: + To see deprecations generated during container compilation and cache warmup, use the --deprecations option: - php %command.full_name% --deprecations + php %command.full_name% --deprecations -To get specific information about a service, specify its name: + To get specific information about a service, specify its name: - php %command.full_name% validator + php %command.full_name% validator -To get specific information about a service including all its arguments, use the --show-arguments flag: + To get specific information about a service including all its arguments, use the --show-arguments flag: - php %command.full_name% validator --show-arguments + php %command.full_name% validator --show-arguments -To see available types that can be used for autowiring, use the --types flag: + To see available types that can be used for autowiring, use the --types flag: - php %command.full_name% --types + php %command.full_name% --types -To see environment variables used by the container, use the --env-vars flag: + To see environment variables used by the container, use the --env-vars flag: - php %command.full_name% --env-vars + php %command.full_name% --env-vars -Display a specific environment variable by specifying its name with the --env-var option: + Display a specific environment variable by specifying its name with the --env-var option: - php %command.full_name% --env-var=APP_ENV + php %command.full_name% --env-var=APP_ENV -Use the --tags option to display tagged public services grouped by tag: + Use the --tags option to display tagged public services grouped by tag: - php %command.full_name% --tags + php %command.full_name% --tags -Find all services with a specific tag by specifying the tag name with the --tag option: + Find all services with a specific tag by specifying the tag name with the --tag option: - php %command.full_name% --tag=form.type + php %command.full_name% --tag=form.type -Use the --parameters option to display all parameters: + Use the --parameters option to display all parameters: - php %command.full_name% --parameters + php %command.full_name% --parameters -Display a specific parameter by specifying its name with the --parameter option: + Display a specific parameter by specifying its name with the --parameter option: - php %command.full_name% --parameter=kernel.debug + php %command.full_name% --parameter=kernel.debug -By default, internal services are hidden. You can display them -using the --show-hidden flag: + By default, internal services are hidden. You can display them + using the --show-hidden flag: - php %command.full_name% --show-hidden + php %command.full_name% --show-hidden -The --format option specifies the format of the command output: + The --format option specifies the format of the command output: - php %command.full_name% --format=json -EOF + php %command.full_name% --format=json + EOF ) ; } diff --git a/Command/ContainerLintCommand.php b/Command/ContainerLintCommand.php index d71fd6810..2fc0be7c5 100644 --- a/Command/ContainerLintCommand.php +++ b/Command/ContainerLintCommand.php @@ -25,7 +25,6 @@ use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\Compiler\ResolveFactoryClassPass; use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass; -use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -82,9 +81,10 @@ private function getContainerBuilder(bool $resolveEnvVars): ContainerBuilder } $kernel = $this->getApplication()->getKernel(); - $kernelContainer = $kernel->getContainer(); + $container = $kernel->getContainer(); + $file = $kernel->isDebug() ? $container->getParameter('debug.container.dump') : false; - if (!$kernel->isDebug() || !$kernelContainer->getParameter('debug.container.dump') || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) { + if (!$file || !(new ConfigCache($file, 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)); } @@ -96,15 +96,20 @@ private function getContainerBuilder(bool $resolveEnvVars): ContainerBuilder }, $kernel, $kernel::class); $container = $buildContainer(); } else { - if (!$kernelContainer instanceof Container) { - throw new RuntimeException(\sprintf('This command does not support the application container: "%s" does not extend "%s".', get_debug_type($kernelContainer), Container::class)); + if (str_ends_with($file, '.xml') && is_file(substr_replace($file, '.ser', -4))) { + $container = unserialize(file_get_contents(substr_replace($file, '.ser', -4))); + } else { + (new XmlFileLoader($container = new ContainerBuilder(new EnvPlaceholderParameterBag()), new FileLocator()))->load($file); } - (new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernelContainer->getParameter('debug.container.dump')); + if (!$container instanceof ContainerBuilder) { + throw new RuntimeException(\sprintf('This command does not support the application container: "%s" is not a "%s".', get_debug_type($container), ContainerBuilder::class)); + } if ($resolveEnvVars) { $container->getCompilerPassConfig()->setOptimizationPasses([new ResolveParameterPlaceHoldersPass(), new ResolveFactoryClassPass()]); } else { + $parameterBag = $container->getParameterBag(); $refl = new \ReflectionProperty($parameterBag, 'resolved'); $refl->setValue($parameterBag, true); diff --git a/Command/DebugAutowiringCommand.php b/Command/DebugAutowiringCommand.php index e159c5a39..5c1869c6a 100644 --- a/Command/DebugAutowiringCommand.php +++ b/Command/DebugAutowiringCommand.php @@ -20,7 +20,6 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; /** @@ -48,16 +47,16 @@ protected function configure(): void new InputOption('all', null, InputOption::VALUE_NONE, 'Show also services that are not aliased'), ]) ->setHelp(<<<'EOF' -The %command.name% command displays the classes and interfaces that -you can use as type-hints for autowiring: + The %command.name% command displays the classes and interfaces that + you can use as type-hints for autowiring: - php %command.full_name% + php %command.full_name% -You can also pass a search term to filter the list: + You can also pass a search term to filter the list: - php %command.full_name% log + php %command.full_name% log -EOF + EOF ) ; } @@ -137,7 +136,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $target = substr($id, \strlen($previousId) + 3); - if ($previousId.' $'.(new Target($target))->getParsedName() === $serviceId) { + if ($container->findDefinition($id) === $container->findDefinition($serviceId)) { $serviceLine .= ' - target:'.$target.''; break; } @@ -185,7 +184,7 @@ private function getFileLink(string $class): string return ''; } - return (string) $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()); + return $r->getFileName() ? ($this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()) ?: '') : ''; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void diff --git a/Command/EventDispatcherDebugCommand.php b/Command/EventDispatcherDebugCommand.php index 3c51cb1b7..43766ed92 100644 --- a/Command/EventDispatcherDebugCommand.php +++ b/Command/EventDispatcherDebugCommand.php @@ -53,18 +53,18 @@ protected function configure(): void new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), ]) ->setHelp(<<<'EOF' -The %command.name% command displays all configured listeners: + The %command.name% command displays all configured listeners: - php %command.full_name% + php %command.full_name% -To get specific listeners for an event, specify its name: + To get specific listeners for an event, specify its name: - php %command.full_name% kernel.request + php %command.full_name% kernel.request -The --format option specifies the format of the command output: + The --format option specifies the format of the command output: - php %command.full_name% --format=json -EOF + php %command.full_name% --format=json + EOF ) ; } diff --git a/Command/RouterDebugCommand.php b/Command/RouterDebugCommand.php index e54377115..3daf865b3 100644 --- a/Command/RouterDebugCommand.php +++ b/Command/RouterDebugCommand.php @@ -58,14 +58,14 @@ protected function configure(): void new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Filter by HTTP method', '', ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']), ]) ->setHelp(<<<'EOF' -The %command.name% displays the configured routes: + The %command.name% displays the configured routes: - php %command.full_name% + php %command.full_name% -The --format option specifies the format of the command output: + The --format option specifies the format of the command output: - php %command.full_name% --format=json -EOF + php %command.full_name% --format=json + EOF ) ; } diff --git a/Command/RouterMatchCommand.php b/Command/RouterMatchCommand.php index 3f0ea3cb5..dee448517 100644 --- a/Command/RouterMatchCommand.php +++ b/Command/RouterMatchCommand.php @@ -53,15 +53,15 @@ protected function configure(): void new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Set the URI host'), ]) ->setHelp(<<<'EOF' -The %command.name% shows which routes match a given request and which don't and for what reason: + The %command.name% shows which routes match a given request and which don't and for what reason: - php %command.full_name% /foo + php %command.full_name% /foo -or + or - php %command.full_name% /foo --method POST --scheme https --host symfony.com --verbose + php %command.full_name% /foo --method POST --scheme https --host symfony.com --verbose -EOF + EOF ) ; } diff --git a/Command/SecretsDecryptToLocalCommand.php b/Command/SecretsDecryptToLocalCommand.php index 4e392b677..3dee29450 100644 --- a/Command/SecretsDecryptToLocalCommand.php +++ b/Command/SecretsDecryptToLocalCommand.php @@ -40,14 +40,14 @@ protected function configure(): void $this ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force overriding of secrets that already exist in the local vault') ->setHelp(<<<'EOF' -The %command.name% command decrypts all secrets and copies them in the local vault. + The %command.name% command decrypts all secrets and copies them in the local vault. - %command.full_name% + %command.full_name% -When the --force option is provided, secrets that already exist in the local vault are overridden. + When the --force option is provided, secrets that already exist in the local vault are overridden. - %command.full_name% --force -EOF + %command.full_name% --force + EOF ) ; } diff --git a/Command/SecretsEncryptFromLocalCommand.php b/Command/SecretsEncryptFromLocalCommand.php index 9740098e5..248f10966 100644 --- a/Command/SecretsEncryptFromLocalCommand.php +++ b/Command/SecretsEncryptFromLocalCommand.php @@ -38,10 +38,10 @@ protected function configure(): void { $this ->setHelp(<<<'EOF' -The %command.name% command encrypts all locally overridden secrets to the vault. + The %command.name% command encrypts all locally overridden secrets to the vault. - %command.full_name% -EOF + %command.full_name% + EOF ) ; } diff --git a/Command/SecretsGenerateKeysCommand.php b/Command/SecretsGenerateKeysCommand.php index f721c786e..e0d5d9c52 100644 --- a/Command/SecretsGenerateKeysCommand.php +++ b/Command/SecretsGenerateKeysCommand.php @@ -43,16 +43,16 @@ protected function configure(): void ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') ->addOption('rotate', 'r', InputOption::VALUE_NONE, 'Re-encrypt existing secrets with the newly generated keys.') ->setHelp(<<<'EOF' -The %command.name% command generates a new encryption key. + The %command.name% command generates a new encryption key. - %command.full_name% + %command.full_name% -If encryption keys already exist, the command must be called with -the --rotate option in order to override those keys and re-encrypt -existing secrets. + If encryption keys already exist, the command must be called with + the --rotate option in order to override those keys and re-encrypt + existing secrets. - %command.full_name% --rotate -EOF + %command.full_name% --rotate + EOF ) ; } diff --git a/Command/SecretsListCommand.php b/Command/SecretsListCommand.php index 920b3b1fc..9057f58d1 100644 --- a/Command/SecretsListCommand.php +++ b/Command/SecretsListCommand.php @@ -43,14 +43,14 @@ protected function configure(): void $this ->addOption('reveal', 'r', InputOption::VALUE_NONE, 'Display decrypted values alongside names') ->setHelp(<<<'EOF' -The %command.name% command list all stored secrets. + The %command.name% command list all stored secrets. - %command.full_name% + %command.full_name% -When the option --reveal is provided, the decrypted secrets are also displayed. + When the option --reveal is provided, the decrypted secrets are also displayed. - %command.full_name% --reveal -EOF + %command.full_name% --reveal + EOF ) ; } diff --git a/Command/SecretsRemoveCommand.php b/Command/SecretsRemoveCommand.php index 59bbe8211..2c3bbb18e 100644 --- a/Command/SecretsRemoveCommand.php +++ b/Command/SecretsRemoveCommand.php @@ -45,10 +45,10 @@ protected function configure(): void ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') ->setHelp(<<<'EOF' -The %command.name% command removes a secret from the vault. + The %command.name% command removes a secret from the vault. - %command.full_name% -EOF + %command.full_name% + EOF ) ; } diff --git a/Command/SecretsRevealCommand.php b/Command/SecretsRevealCommand.php index c2110ee76..8a678e14d 100644 --- a/Command/SecretsRevealCommand.php +++ b/Command/SecretsRevealCommand.php @@ -38,10 +38,10 @@ protected function configure(): void $this ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret to reveal', null, fn () => array_keys($this->vault->list())) ->setHelp(<<<'EOF' -The %command.name% command reveals a stored secret. + The %command.name% command reveals a stored secret. - %command.full_name% -EOF + %command.full_name% + EOF ) ; } diff --git a/Command/SecretsSetCommand.php b/Command/SecretsSetCommand.php index f7e8eeaa6..c9eabb25a 100644 --- a/Command/SecretsSetCommand.php +++ b/Command/SecretsSetCommand.php @@ -48,24 +48,24 @@ protected function configure(): void ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') ->addOption('random', 'r', InputOption::VALUE_OPTIONAL, 'Generate a random value.', false) ->setHelp(<<<'EOF' -The %command.name% command stores a secret in the vault. + The %command.name% command stores a secret in the vault. - %command.full_name% + %command.full_name% -To reference secrets in services.yaml or any other config -files, use "%env()%". + To reference secrets in services.yaml or any other config + files, use "%env()%". -By default, the secret value should be entered interactively. -Alternatively, provide a file where to read the secret from: + By default, the secret value should be entered interactively. + Alternatively, provide a file where to read the secret from: - php %command.full_name% filename + php %command.full_name% filename -Use "-" as a file name to read from STDIN: + Use "-" as a file name to read from STDIN: - cat filename | php %command.full_name% - + cat filename | php %command.full_name% - -Use --local to override secrets for local needs. -EOF + Use --local to override secrets for local needs. + EOF ) ; } diff --git a/Command/TranslationDebugCommand.php b/Command/TranslationDebugCommand.php index a320130d5..b186646a3 100644 --- a/Command/TranslationDebugCommand.php +++ b/Command/TranslationDebugCommand.php @@ -75,35 +75,35 @@ protected function configure(): void new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'), ]) ->setHelp(<<<'EOF' -The %command.name% command helps finding unused or missing translation -messages and comparing them with the fallback ones by inspecting the -templates and translation files of a given bundle or the default translations directory. + The %command.name% command helps finding unused or missing translation + messages and comparing them with the fallback ones by inspecting the + templates and translation files of a given bundle or the default translations directory. -You can display information about bundle translations in a specific locale: + You can display information about bundle translations in a specific locale: - php %command.full_name% en AcmeDemoBundle + php %command.full_name% en AcmeDemoBundle -You can also specify a translation domain for the search: + You can also specify a translation domain for the search: - php %command.full_name% --domain=messages en AcmeDemoBundle + php %command.full_name% --domain=messages en AcmeDemoBundle -You can only display missing messages: + You can only display missing messages: - php %command.full_name% --only-missing en AcmeDemoBundle + php %command.full_name% --only-missing en AcmeDemoBundle -You can only display unused messages: + You can only display unused messages: - php %command.full_name% --only-unused en AcmeDemoBundle + php %command.full_name% --only-unused en AcmeDemoBundle -You can display information about application translations in a specific locale: + You can display information about application translations in a specific locale: - php %command.full_name% en + php %command.full_name% en -You can display information about translations in all registered bundles in a specific locale: + You can display information about translations in all registered bundles in a specific locale: - php %command.full_name% --all en + php %command.full_name% --all en -EOF + EOF ) ; } diff --git a/Command/TranslationExtractCommand.php b/Command/TranslationExtractCommand.php index c8e61b61a..0cd780734 100644 --- a/Command/TranslationExtractCommand.php +++ b/Command/TranslationExtractCommand.php @@ -83,34 +83,34 @@ protected function configure(): void new InputOption('as-tree', null, InputOption::VALUE_REQUIRED, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'), ]) ->setHelp(<<<'EOF' -The %command.name% command extracts translation strings from templates -of a given bundle or the default translations directory. It can display them or merge -the new ones into the translation files. + The %command.name% command extracts translation strings from templates + of a given bundle or the default translations directory. It can display them or merge + the new ones into the translation files. -When new translation strings are found it can automatically add a prefix to the translation -message. However, if the --no-fill option is used, the --prefix -option has no effect, since the translation values are left empty. + When new translation strings are found it can automatically add a prefix to the translation + message. However, if the --no-fill option is used, the --prefix + option has no effect, since the translation values are left empty. -Example running against a Bundle (AcmeBundle) + Example running against a Bundle (AcmeBundle) - php %command.full_name% --dump-messages en AcmeBundle - php %command.full_name% --force --prefix="new_" fr AcmeBundle + php %command.full_name% --dump-messages en AcmeBundle + php %command.full_name% --force --prefix="new_" fr AcmeBundle -Example running against default messages directory + Example running against default messages directory - php %command.full_name% --dump-messages en - php %command.full_name% --force --prefix="new_" fr + php %command.full_name% --dump-messages en + php %command.full_name% --force --prefix="new_" fr -You can sort the output with the --sort flag: + You can sort the output with the --sort flag: - php %command.full_name% --dump-messages --sort=asc en AcmeBundle - php %command.full_name% --force --sort=desc fr + php %command.full_name% --dump-messages --sort=asc en AcmeBundle + php %command.full_name% --force --sort=desc fr -You can dump a tree-like structure using the yaml format with --as-tree flag: + You can dump a tree-like structure using the yaml format with --as-tree flag: - php %command.full_name% --force --format=yaml --as-tree=3 en AcmeBundle + php %command.full_name% --force --format=yaml --as-tree=3 en AcmeBundle -EOF + EOF ) ; } diff --git a/Command/WorkflowDumpCommand.php b/Command/WorkflowDumpCommand.php index 201fb8be8..06570e9ea 100644 --- a/Command/WorkflowDumpCommand.php +++ b/Command/WorkflowDumpCommand.php @@ -59,13 +59,13 @@ protected function configure(): void new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format ['.implode('|', self::DUMP_FORMAT_OPTIONS).']', 'dot'), ]) ->setHelp(<<<'EOF' -The %command.name% command dumps the graphical representation of a -workflow in different formats + The %command.name% command dumps the graphical representation of a + workflow in different formats -DOT: %command.full_name% | dot -Tpng > workflow.png -PUML: %command.full_name% --dump-format=puml | java -jar plantuml.jar -p > workflow.png -MERMAID: %command.full_name% --dump-format=mermaid | mmdc -o workflow.svg -EOF + DOT: %command.full_name% | dot -Tpng > workflow.png + PUML: %command.full_name% --dump-format=puml | java -jar plantuml.jar -p > workflow.png + MERMAID: %command.full_name% --dump-format=mermaid | mmdc -o workflow.svg + EOF ) ; } diff --git a/Command/XliffLintCommand.php b/Command/XliffLintCommand.php index 5b094f165..9bbe39db1 100644 --- a/Command/XliffLintCommand.php +++ b/Command/XliffLintCommand.php @@ -47,11 +47,11 @@ protected function configure(): void $this->setHelp($this->getHelp().<<<'EOF' -Or find all files in a bundle: + Or find all files in a bundle: - php %command.full_name% @AcmeDemoBundle + php %command.full_name% @AcmeDemoBundle -EOF + EOF ); } } diff --git a/Command/YamlLintCommand.php b/Command/YamlLintCommand.php index 141390812..5948add7c 100644 --- a/Command/YamlLintCommand.php +++ b/Command/YamlLintCommand.php @@ -46,11 +46,11 @@ protected function configure(): void $this->setHelp($this->getHelp().<<<'EOF' -Or find all files in a bundle: + Or find all files in a bundle: - php %command.full_name% @AcmeDemoBundle + php %command.full_name% @AcmeDemoBundle -EOF + EOF ); } } diff --git a/Console/Application.php b/Console/Application.php index 274e7b06d..8eb3808a5 100644 --- a/Console/Application.php +++ b/Console/Application.php @@ -159,11 +159,29 @@ public function getLongVersion(): string return parent::getLongVersion().\sprintf(' (env: %s, debug: %s)', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); } + /** + * @deprecated since Symfony 7.4, use Application::addCommand() instead + */ public function add(Command $command): ?Command + { + trigger_deprecation('symfony/framework-bundle', '7.4', 'The "%s()" method is deprecated and will be removed in Symfony 8.0, use "%s::addCommand()" instead.', __METHOD__, self::class); + + return $this->addCommand($command); + } + + public function addCommand(callable|Command $command): ?Command { $this->registerCommands(); - return parent::add($command); + if (!method_exists(BaseApplication::class, 'addCommand')) { + if (!$command instanceof Command) { + throw new \LogicException('Using callables as commands requires symfony/console 7.4 or higher.'); + } + + return parent::add($command); + } + + return parent::addCommand($command); } protected function registerCommands(): void @@ -197,7 +215,7 @@ protected function registerCommands(): void foreach ($container->getParameter('console.command.ids') as $id) { if (!isset($lazyCommandIds[$id])) { try { - $this->add($container->get($id)); + $this->addCommand($container->get($id)); } catch (\Throwable $e) { $this->registrationErrors[] = $e; } diff --git a/Console/Descriptor/TextDescriptor.php b/Console/Descriptor/TextDescriptor.php index 12b345411..a5b31b186 100644 --- a/Console/Descriptor/TextDescriptor.php +++ b/Console/Descriptor/TextDescriptor.php @@ -576,6 +576,9 @@ private function formatRouterConfig(array $config): string return trim($configAsString); } + /** + * @param (callable():ContainerBuilder)|null $getContainer + */ private function formatControllerLink(mixed $controller, string $anchorText, ?callable $getContainer = null): string { if (null === $this->fileLinkFormatter) { diff --git a/Console/Descriptor/XmlDescriptor.php b/Console/Descriptor/XmlDescriptor.php index 8daa61d2a..6a25ae3a3 100644 --- a/Console/Descriptor/XmlDescriptor.php +++ b/Console/Descriptor/XmlDescriptor.php @@ -288,6 +288,9 @@ private function getContainerServiceDocument(object $service, string $id, ?Conta return $dom; } + /** + * @param (callable(string):bool)|null $filter + */ private function getContainerServicesDocument(ContainerBuilder $container, ?string $tag = null, bool $showHidden = false, ?callable $filter = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); diff --git a/Controller/AbstractController.php b/Controller/AbstractController.php index de7395d5a..c44028f8c 100644 --- a/Controller/AbstractController.php +++ b/Controller/AbstractController.php @@ -67,18 +67,6 @@ public function setContainer(ContainerInterface $container): ?ContainerInterface return $previous; } - /** - * Gets a container parameter by its name. - */ - protected function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null - { - if (!$this->container->has('parameter_bag')) { - throw new ServiceNotFoundException('parameter_bag.', null, null, [], \sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', static::class)); - } - - return $this->container->get('parameter_bag')->get($name); - } - public static function getSubscribedServices(): array { return [ @@ -96,6 +84,18 @@ public static function getSubscribedServices(): array ]; } + /** + * Gets a container parameter by its name. + */ + protected function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (!$this->container->has('parameter_bag')) { + throw new ServiceNotFoundException('parameter_bag.', null, null, [], \sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', static::class)); + } + + return $this->container->get('parameter_bag')->get($name); + } + /** * Generates a URL from the given parameters. * diff --git a/Controller/ControllerHelper.php b/Controller/ControllerHelper.php new file mode 100644 index 000000000..4fc56b6a9 --- /dev/null +++ b/Controller/ControllerHelper.php @@ -0,0 +1,473 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Controller; + +use Psr\Container\ContainerInterface; +use Psr\Link\EvolvableLinkInterface; +use Psr\Link\LinkInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authorization\AccessDecision; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; +use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Contracts\Service\ServiceSubscriberInterface; +use Twig\Environment; + +/** + * Provides the helpers from AbstractControler as a standalone service. + * + * Best used together with #[AutowireMethodOf] to remove any coupling. + */ +class ControllerHelper implements ServiceSubscriberInterface +{ + public function __construct( + private ContainerInterface $container, + ) { + } + + public static function getSubscribedServices(): array + { + return [ + 'router' => '?'.RouterInterface::class, + 'request_stack' => '?'.RequestStack::class, + 'http_kernel' => '?'.HttpKernelInterface::class, + 'serializer' => '?'.SerializerInterface::class, + 'security.authorization_checker' => '?'.AuthorizationCheckerInterface::class, + 'twig' => '?'.Environment::class, + 'form.factory' => '?'.FormFactoryInterface::class, + 'security.token_storage' => '?'.TokenStorageInterface::class, + 'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class, + 'parameter_bag' => '?'.ContainerBagInterface::class, + 'web_link.http_header_serializer' => '?'.HttpHeaderSerializer::class, + ]; + } + + /** + * Gets a container parameter by its name. + */ + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (!$this->container->has('parameter_bag')) { + throw new ServiceNotFoundException('parameter_bag.', null, null, [], \sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', static::class)); + } + + return $this->container->get('parameter_bag')->get($name); + } + + /** + * Generates a URL from the given parameters. + * + * @see UrlGeneratorInterface + */ + public function generateUrl(string $route, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string + { + return $this->container->get('router')->generate($route, $parameters, $referenceType); + } + + /** + * Forwards the request to another controller. + * + * @param string $controller The controller name (a string like "App\Controller\PostController::index" or "App\Controller\PostController" if it is invokable) + */ + public function forward(string $controller, array $path = [], array $query = []): Response + { + $request = $this->container->get('request_stack')->getCurrentRequest(); + $path['_controller'] = $controller; + $subRequest = $request->duplicate($query, null, $path); + + return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + } + + /** + * Returns a RedirectResponse to the given URL. + * + * @param int $status The HTTP status code (302 "Found" by default) + */ + public function redirect(string $url, int $status = 302): RedirectResponse + { + return new RedirectResponse($url, $status); + } + + /** + * Returns a RedirectResponse to the given route with the given parameters. + * + * @param int $status The HTTP status code (302 "Found" by default) + */ + public function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse + { + return $this->redirect($this->generateUrl($route, $parameters), $status); + } + + /** + * Returns a JsonResponse that uses the serializer component if enabled, or json_encode. + * + * @param int $status The HTTP status code (200 "OK" by default) + */ + public function json(mixed $data, int $status = 200, array $headers = [], array $context = []): JsonResponse + { + if ($this->container->has('serializer')) { + $json = $this->container->get('serializer')->serialize($data, 'json', array_merge([ + 'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS, + ], $context)); + + return new JsonResponse($json, $status, $headers, true); + } + + return new JsonResponse($data, $status, $headers); + } + + /** + * Returns a BinaryFileResponse object with original or customized file name and disposition header. + */ + public function file(\SplFileInfo|string $file, ?string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse + { + $response = new BinaryFileResponse($file); + $response->setContentDisposition($disposition, $fileName ?? $response->getFile()->getFilename()); + + return $response; + } + + /** + * Adds a flash message to the current session for type. + * + * @throws \LogicException + */ + public function addFlash(string $type, mixed $message): void + { + try { + $session = $this->container->get('request_stack')->getSession(); + } catch (SessionNotFoundException $e) { + throw new \LogicException('You cannot use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".', 0, $e); + } + + if (!$session instanceof FlashBagAwareSessionInterface) { + throw new \LogicException(\sprintf('You cannot use the addFlash method because class "%s" doesn\'t implement "%s".', get_debug_type($session), FlashBagAwareSessionInterface::class)); + } + + $session->getFlashBag()->add($type, $message); + } + + /** + * Checks if the attribute is granted against the current authentication token and optionally supplied subject. + * + * @throws \LogicException + */ + public function isGranted(mixed $attribute, mixed $subject = null): bool + { + if (!$this->container->has('security.authorization_checker')) { + throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); + } + + return $this->container->get('security.authorization_checker')->isGranted($attribute, $subject); + } + + /** + * Checks if the attribute is granted against the current authentication token and optionally supplied subject. + */ + public function getAccessDecision(mixed $attribute, mixed $subject = null): AccessDecision + { + if (!$this->container->has('security.authorization_checker')) { + throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); + } + + $accessDecision = new AccessDecision(); + $accessDecision->isGranted = $this->container->get('security.authorization_checker')->isGranted($attribute, $subject, $accessDecision); + + return $accessDecision; + } + + /** + * Throws an exception unless the attribute is granted against the current authentication token and optionally + * supplied subject. + * + * @throws AccessDeniedException + */ + public function denyAccessUnlessGranted(mixed $attribute, mixed $subject = null, string $message = 'Access Denied.'): void + { + if (class_exists(AccessDecision::class)) { + $accessDecision = $this->getAccessDecision($attribute, $subject); + $isGranted = $accessDecision->isGranted; + } else { + $accessDecision = null; + $isGranted = $this->isGranted($attribute, $subject); + } + + if (!$isGranted) { + $e = $this->createAccessDeniedException(3 > \func_num_args() && $accessDecision ? $accessDecision->getMessage() : $message); + $e->setAttributes([$attribute]); + $e->setSubject($subject); + + if ($accessDecision) { + $e->setAccessDecision($accessDecision); + } + + throw $e; + } + } + + /** + * Returns a rendered view. + * + * Forms found in parameters are auto-cast to form views. + */ + public function renderView(string $view, array $parameters = []): string + { + return $this->doRenderView($view, null, $parameters, __FUNCTION__); + } + + /** + * Returns a rendered block from a view. + * + * Forms found in parameters are auto-cast to form views. + */ + public function renderBlockView(string $view, string $block, array $parameters = []): string + { + return $this->doRenderView($view, $block, $parameters, __FUNCTION__); + } + + /** + * Renders a view. + * + * If an invalid form is found in the list of parameters, a 422 status code is returned. + * Forms found in parameters are auto-cast to form views. + */ + public function render(string $view, array $parameters = [], ?Response $response = null): Response + { + return $this->doRender($view, null, $parameters, $response, __FUNCTION__); + } + + /** + * Renders a block in a view. + * + * If an invalid form is found in the list of parameters, a 422 status code is returned. + * Forms found in parameters are auto-cast to form views. + */ + public function renderBlock(string $view, string $block, array $parameters = [], ?Response $response = null): Response + { + return $this->doRender($view, $block, $parameters, $response, __FUNCTION__); + } + + /** + * Streams a view. + */ + public function stream(string $view, array $parameters = [], ?StreamedResponse $response = null): StreamedResponse + { + if (!$this->container->has('twig')) { + throw new \LogicException('You cannot use the "stream" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); + } + + $twig = $this->container->get('twig'); + + $callback = function () use ($twig, $view, $parameters) { + $twig->display($view, $parameters); + }; + + if (null === $response) { + return new StreamedResponse($callback); + } + + $response->setCallback($callback); + + return $response; + } + + /** + * Returns a NotFoundHttpException. + * + * This will result in a 404 response code. Usage example: + * + * throw $this->createNotFoundException('Page not found!'); + */ + public function createNotFoundException(string $message = 'Not Found', ?\Throwable $previous = null): NotFoundHttpException + { + return new NotFoundHttpException($message, $previous); + } + + /** + * Returns an AccessDeniedException. + * + * This will result in a 403 response code. Usage example: + * + * throw $this->createAccessDeniedException('Unable to access this page!'); + * + * @throws \LogicException If the Security component is not available + */ + public function createAccessDeniedException(string $message = 'Access Denied.', ?\Throwable $previous = null): AccessDeniedException + { + if (!class_exists(AccessDeniedException::class)) { + throw new \LogicException('You cannot use the "createAccessDeniedException" method if the Security component is not available. Try running "composer require symfony/security-bundle".'); + } + + return new AccessDeniedException($message, $previous); + } + + /** + * Creates and returns a Form instance from the type of the form. + */ + public function createForm(string $type, mixed $data = null, array $options = []): FormInterface + { + return $this->container->get('form.factory')->create($type, $data, $options); + } + + /** + * Creates and returns a form builder instance. + */ + public function createFormBuilder(mixed $data = null, array $options = []): FormBuilderInterface + { + return $this->container->get('form.factory')->createBuilder(FormType::class, $data, $options); + } + + /** + * Get a user from the Security Token Storage. + * + * @throws \LogicException If SecurityBundle is not available + * + * @see TokenInterface::getUser() + */ + public function getUser(): ?UserInterface + { + if (!$this->container->has('security.token_storage')) { + throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); + } + + if (null === $token = $this->container->get('security.token_storage')->getToken()) { + return null; + } + + return $token->getUser(); + } + + /** + * Checks the validity of a CSRF token. + * + * @param string $id The id used when generating the token + * @param string|null $token The actual token sent with the request that should be validated + */ + public function isCsrfTokenValid(string $id, #[\SensitiveParameter] ?string $token): bool + { + if (!$this->container->has('security.csrf.token_manager')) { + throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".'); + } + + return $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($id, $token)); + } + + /** + * Adds a Link HTTP header to the current response. + * + * @see https://tools.ietf.org/html/rfc5988 + */ + public function addLink(Request $request, LinkInterface $link): void + { + if (!class_exists(AddLinkHeaderListener::class)) { + throw new \LogicException('You cannot use the "addLink" method if the WebLink component is not available. Try running "composer require symfony/web-link".'); + } + + if (null === $linkProvider = $request->attributes->get('_links')) { + $request->attributes->set('_links', new GenericLinkProvider([$link])); + + return; + } + + $request->attributes->set('_links', $linkProvider->withLink($link)); + } + + /** + * @param LinkInterface[] $links + */ + public function sendEarlyHints(iterable $links = [], ?Response $response = null): 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 = []; + 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; + } + + private function doRenderView(string $view, ?string $block, array $parameters, string $method): string + { + if (!$this->container->has('twig')) { + throw new \LogicException(\sprintf('You cannot use the "%s" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".', $method)); + } + + foreach ($parameters as $k => $v) { + if ($v instanceof FormInterface) { + $parameters[$k] = $v->createView(); + } + } + + if (null !== $block) { + return $this->container->get('twig')->load($view)->renderBlock($block, $parameters); + } + + return $this->container->get('twig')->render($view, $parameters); + } + + private function doRender(string $view, ?string $block, array $parameters, ?Response $response, string $method): Response + { + $content = $this->doRenderView($view, $block, $parameters, $method); + $response ??= new Response(); + + if (200 === $response->getStatusCode()) { + foreach ($parameters as $v) { + if ($v instanceof FormInterface && $v->isSubmitted() && !$v->isValid()) { + $response->setStatusCode(422); + break; + } + } + } + + $response->setContent($content); + + return $response; + } +} diff --git a/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php b/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php index e4023e623..456305bc9 100644 --- a/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php +++ b/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php @@ -13,8 +13,11 @@ use Symfony\Component\Config\ConfigCache; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ResolveEnvPlaceholdersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\XmlDumper; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\Filesystem\Filesystem; /** * Dumps the ContainerBuilder to a cache file so that it can be used by @@ -31,9 +34,52 @@ public function process(ContainerBuilder $container): void return; } - $cache = new ConfigCache($container->getParameter('debug.container.dump'), true); - if (!$cache->isFresh()) { - $cache->write((new XmlDumper($container))->dump(), $container->getResources()); + $file = $container->getParameter('debug.container.dump'); + $cache = new ConfigCache($file, true); + if ($cache->isFresh()) { + return; + } + $cache->write((new XmlDumper($container))->dump(), $container->getResources()); + + if (!str_ends_with($file, '.xml')) { + return; + } + + $file = substr_replace($file, '.ser', -4); + + try { + $dump = new ContainerBuilder(clone $container->getParameterBag()); + $dump->setDefinitions(unserialize(serialize($container->getDefinitions()))); + $dump->setAliases($container->getAliases()); + + if (($bag = $container->getParameterBag()) instanceof EnvPlaceholderParameterBag) { + (new ResolveEnvPlaceholdersPass(null))->process($dump); + $dump->__construct(new EnvPlaceholderParameterBag($container->resolveEnvPlaceholders($this->escapeParameters($bag->all())))); + } + + $fs = new Filesystem(); + $fs->dumpFile($file, serialize($dump)); + $fs->chmod($file, 0o666, umask()); + } catch (\Throwable $e) { + $container->getCompiler()->log($this, $e->getMessage()); + // ignore serialization and file-system errors + if (file_exists($file)) { + @unlink($file); + } } } + + private function escapeParameters(array $parameters): array + { + $params = []; + foreach ($parameters as $k => $v) { + $params[$k] = match (true) { + \is_array($v) => $this->escapeParameters($v), + \is_string($v) => str_replace('%', '%%', $v), + default => $v, + }; + } + + return $params; + } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index d66a5c6ff..0181b18a1 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -2562,7 +2562,7 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $ ->end() ->end() ->validate() - ->ifTrue(static fn ($v) => !\in_array($v['policy'], ['no_limit', 'compound']) && !isset($v['limit'])) + ->ifTrue(static fn ($v) => !\in_array($v['policy'], ['no_limit', 'compound'], true) && !isset($v['limit'])) ->thenInvalid('A limit must be provided when using a policy different than "compound" or "no_limit".') ->end() ->end() diff --git a/DependencyInjection/FrameworkExtension.php b/DependencyInjection/FrameworkExtension.php index a85642ec2..e21b8b838 100644 --- a/DependencyInjection/FrameworkExtension.php +++ b/DependencyInjection/FrameworkExtension.php @@ -33,6 +33,7 @@ use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface; use Symfony\Bundle\FullStack; use Symfony\Bundle\MercureBundle\MercureBundle; +use Symfony\Component\Asset\Package; use Symfony\Component\Asset\PackageInterface; use Symfony\Component\AssetMapper\AssetMapper; use Symfony\Component\AssetMapper\Compiler\AssetCompilerInterface; @@ -61,7 +62,6 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; -use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; @@ -134,6 +134,8 @@ use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Middleware\DeduplicateMiddleware; use Symfony\Component\Messenger\Middleware\RouterContextMiddleware; +use Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory; +use Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use Symfony\Component\Messenger\Transport\TransportFactoryInterface as MessengerTransportFactoryInterface; use Symfony\Component\Messenger\Transport\TransportInterface; @@ -177,6 +179,7 @@ use Symfony\Component\Scheduler\Messenger\Serializer\Normalizer\SchedulerTriggerNormalizer; use Symfony\Component\Security\Core\AuthenticationEvents; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Semaphore\PersistingStoreInterface as SemaphoreStoreInterface; use Symfony\Component\Semaphore\Semaphore; @@ -217,6 +220,7 @@ use Symfony\Component\Validator\ObjectInitializerInterface; use Symfony\Component\Validator\Validation; use Symfony\Component\Webhook\Controller\WebhookController; +use Symfony\Component\WebLink\HttpHeaderParser; use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow; use Symfony\Component\Workflow\WorkflowInterface; @@ -389,7 +393,7 @@ public function load(array $configs, ContainerBuilder $container): void } if ($this->readConfigEnabled('assets', $container, $config['assets'])) { - if (!class_exists(\Symfony\Component\Asset\Package::class)) { + if (!class_exists(Package::class)) { throw new LogicException('Asset support cannot be enabled as the Asset component is not installed. Try running "composer require symfony/asset".'); } @@ -502,6 +506,11 @@ public function load(array $configs, ContainerBuilder $container): void } $loader->load('web_link.php'); + + // Require symfony/web-link 7.4 + if (!class_exists(HttpHeaderParser::class)) { + $container->removeDefinition('web_link.http_header_parser'); + } } if ($this->readConfigEnabled('uid', $container, $config['uid'])) { @@ -589,9 +598,9 @@ public function load(array $configs, ContainerBuilder $container): void $container->removeDefinition('cache.messenger.restart_workers_signal'); if ($container->hasDefinition('messenger.transport.amqp.factory') && !class_exists(MessengerBridge\Amqp\Transport\AmqpTransportFactory::class)) { - if (class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)) { + if (class_exists(AmqpTransportFactory::class)) { $container->getDefinition('messenger.transport.amqp.factory') - ->setClass(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class) + ->setClass(AmqpTransportFactory::class) ->addTag('messenger.transport_factory'); } else { $container->removeDefinition('messenger.transport.amqp.factory'); @@ -599,9 +608,9 @@ public function load(array $configs, ContainerBuilder $container): void } if ($container->hasDefinition('messenger.transport.redis.factory') && !class_exists(MessengerBridge\Redis\Transport\RedisTransportFactory::class)) { - if (class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)) { + if (class_exists(RedisTransportFactory::class)) { $container->getDefinition('messenger.transport.redis.factory') - ->setClass(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class) + ->setClass(RedisTransportFactory::class) ->addTag('messenger.transport_factory'); } else { $container->removeDefinition('messenger.transport.redis.factory'); @@ -1184,8 +1193,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ // Store to container $container->setDefinition($workflowId, $workflowDefinition); $container->setDefinition($definitionDefinitionId, $definitionDefinition); - $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name.'.'.$type); - $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name); + $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name.'.'.$type, $name); // Add workflow to Registry if ($workflow['supports']) { @@ -1420,7 +1428,7 @@ private function registerAssetsConfiguration(array $config, ContainerBuilder $co $packageDefinition = $this->createPackageDefinition($package['base_path'], $package['base_urls'], $version) ->addTag('assets.package', ['package' => $name]); $container->setDefinition('assets._package_'.$name, $packageDefinition); - $container->registerAliasForArgument('assets._package_'.$name, PackageInterface::class, $name.'.package'); + $container->registerAliasForArgument('assets._package_'.$name, PackageInterface::class, $name.'.package', $name); } } @@ -1967,7 +1975,7 @@ private function registerSecurityCsrfConfiguration(array $config, ContainerBuild return; } - if (!class_exists(\Symfony\Component\Security\Csrf\CsrfToken::class)) { + if (!class_exists(CsrfToken::class)) { throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require symfony/security-csrf".'); } if (!$config['stateless_token_ids'] && !$this->isInitializedConfigEnabled('session')) { @@ -2049,7 +2057,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder } $fileRecorder = function ($extension, $path) use (&$serializerLoaders) { - $definition = new Definition(\in_array($extension, ['yaml', 'yml']) ? YamlFileLoader::class : XmlFileLoader::class, [$path]); + $definition = new Definition(\in_array($extension, ['yaml', 'yml'], true) ? YamlFileLoader::class : XmlFileLoader::class, [$path]); $serializerLoaders[] = $definition; }; @@ -2241,7 +2249,7 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont $container->setAlias('lock.factory', new Alias('lock.'.$resourceName.'.factory', false)); $container->setAlias(LockFactory::class, new Alias('lock.factory', false)); } else { - $container->registerAliasForArgument('lock.'.$resourceName.'.factory', LockFactory::class, $resourceName.'.lock.factory'); + $container->registerAliasForArgument('lock.'.$resourceName.'.factory', LockFactory::class, $resourceName.'.lock.factory', $resourceName); } } } @@ -2276,7 +2284,7 @@ private function registerSemaphoreConfiguration(array $config, ContainerBuilder $container->setAlias('semaphore.factory', new Alias('semaphore.'.$resourceName.'.factory', false)); $container->setAlias(SemaphoreFactory::class, new Alias('semaphore.factory', false)); } else { - $container->registerAliasForArgument('semaphore.'.$resourceName.'.factory', SemaphoreFactory::class, $resourceName.'.semaphore.factory'); + $container->registerAliasForArgument('semaphore.'.$resourceName.'.factory', SemaphoreFactory::class, $resourceName.'.semaphore.factory', $resourceName); } } } @@ -2879,6 +2887,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co MailerBridge\Mailomat\Transport\MailomatTransportFactory::class => 'mailer.transport_factory.mailomat', MailerBridge\MailPace\Transport\MailPaceTransportFactory::class => 'mailer.transport_factory.mailpace', MailerBridge\Mailchimp\Transport\MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', + MailerBridge\MicrosoftGraph\Transport\MicrosoftGraphTransportFactory::class => 'mailer.transport_factory.microsoftgraph', MailerBridge\Postal\Transport\PostalTransportFactory::class => 'mailer.transport_factory.postal', MailerBridge\Postmark\Transport\PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', MailerBridge\Mailtrap\Transport\MailtrapTransportFactory::class => 'mailer.transport_factory.mailtrap', @@ -2931,7 +2940,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co $headers = new Definition(Headers::class); foreach ($config['headers'] as $name => $data) { $value = $data['value']; - if (\in_array(strtolower($name), ['from', 'to', 'cc', 'bcc', 'reply-to'])) { + if (\in_array(strtolower($name), ['from', 'to', 'cc', 'bcc', 'reply-to'], true)) { $value = (array) $value; } $headers->addMethodCall('addHeader', [$name, $value]); @@ -3301,13 +3310,11 @@ private function registerRateLimiterConfiguration(array $config, ContainerBuilde $factoryAlias = $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter'); if (interface_exists(RateLimiterFactoryInterface::class)) { - $container->registerAliasForArgument($limiterId, RateLimiterFactoryInterface::class, $name.'.limiter'); - $factoryAlias->setDeprecated('symfony/framework-bundle', '7.3', \sprintf('The "%%alias_id%%" autowiring alias is deprecated and will be removed in 8.0, use "%s $%s" instead.', RateLimiterFactoryInterface::class, (new Target($name.'.limiter'))->getParsedName())); - $internalAliasId = \sprintf('.%s $%s.limiter', RateLimiterFactory::class, $name); + $container->registerAliasForArgument($limiterId, RateLimiterFactoryInterface::class, $name.'.limiter', $name); - if ($container->hasAlias($internalAliasId)) { - $container->getAlias($internalAliasId)->setDeprecated('symfony/framework-bundle', '7.3', \sprintf('The "%%alias_id%%" autowiring alias is deprecated and will be removed in 8.0, use "%s $%s" instead.', RateLimiterFactoryInterface::class, (new Target($name.'.limiter'))->getParsedName())); - } + $factoryAlias->setDeprecated('symfony/framework-bundle', '7.3', 'The "%alias_id%" autowiring alias is deprecated and will be removed in 8.0, use "RateLimiterFactoryInterface" instead.'); + $container->getAlias(\sprintf('.%s $%s.limiter', RateLimiterFactory::class, $name)) + ->setDeprecated('symfony/framework-bundle', '7.3', 'The "%alias_id%" autowiring alias is deprecated and will be removed in 8.0, use "RateLimiterFactoryInterface" instead.'); } } @@ -3332,7 +3339,7 @@ private function registerRateLimiterConfiguration(array $config, ContainerBuilde ))) ; - $container->registerAliasForArgument($limiterId, RateLimiterFactoryInterface::class, $name.'.limiter'); + $container->registerAliasForArgument($limiterId, RateLimiterFactoryInterface::class, $name.'.limiter', $name); } } diff --git a/FrameworkBundle.php b/FrameworkBundle.php index 300fe22fb..34e8b3ae7 100644 --- a/FrameworkBundle.php +++ b/FrameworkBundle.php @@ -103,8 +103,7 @@ public function boot(): void $_ENV['DOCTRINE_DEPRECATIONS'] = $_SERVER['DOCTRINE_DEPRECATIONS'] ??= 'trigger'; if (class_exists(SymfonyRuntime::class)) { - $handler = set_error_handler('var_dump'); - restore_error_handler(); + $handler = get_error_handler(); } else { $handler = [ErrorHandler::register(null, false)]; } diff --git a/KernelBrowser.php b/KernelBrowser.php index add2508ff..d5b4262a4 100644 --- a/KernelBrowser.php +++ b/KernelBrowser.php @@ -205,25 +205,25 @@ protected function getScript(object $request): string $profilerCode = ''; if ($this->profiler) { $profilerCode = <<<'EOF' -$container = $kernel->getContainer(); -$container = $container->has('test.service_container') ? $container->get('test.service_container') : $container; -$container->get('profiler')->enable(); -EOF; + $container = $kernel->getContainer(); + $container = $container->has('test.service_container') ? $container->get('test.service_container') : $container; + $container->get('profiler')->enable(); + EOF; } $code = <<boot(); -$profilerCode + \$kernel = unserialize($kernel); + \$kernel->boot(); + $profilerCode -\$request = unserialize($request); -EOF; + \$request = unserialize($request); + EOF; return $code.$this->getHandleScript(); } diff --git a/Resources/config/console.php b/Resources/config/console.php index 7ef10bb52..fda2f75d7 100644 --- a/Resources/config/console.php +++ b/Resources/config/console.php @@ -45,6 +45,7 @@ use Symfony\Component\Console\Messenger\RunCommandMessageHandler; use Symfony\Component\Dotenv\Command\DebugCommand as DotenvDebugCommand; use Symfony\Component\ErrorHandler\Command\ErrorDumpCommand; +use Symfony\Component\Form\Command\DebugCommand; use Symfony\Component\Messenger\Command\ConsumeMessagesCommand; use Symfony\Component\Messenger\Command\DebugCommand as MessengerDebugCommand; use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand; @@ -327,7 +328,7 @@ ]) ->tag('console.command') - ->set('console.command.form_debug', \Symfony\Component\Form\Command\DebugCommand::class) + ->set('console.command.form_debug', DebugCommand::class) ->args([ service('form.registry'), [], // All form types namespaces are stored here by FormPass diff --git a/Resources/config/mailer_transports.php b/Resources/config/mailer_transports.php index 2c79b4d55..e88e95166 100644 --- a/Resources/config/mailer_transports.php +++ b/Resources/config/mailer_transports.php @@ -24,6 +24,7 @@ use Symfony\Component\Mailer\Bridge\Mailomat\Transport\MailomatTransportFactory; use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory; use Symfony\Component\Mailer\Bridge\Mailtrap\Transport\MailtrapTransportFactory; +use Symfony\Component\Mailer\Bridge\MicrosoftGraph\Transport\MicrosoftGraphTransportFactory; use Symfony\Component\Mailer\Bridge\Postal\Transport\PostalTransportFactory; use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendTransportFactory; @@ -60,6 +61,7 @@ 'mailjet' => MailjetTransportFactory::class, 'mailomat' => MailomatTransportFactory::class, 'mailpace' => MailPaceTransportFactory::class, + 'microsoftgraph' => MicrosoftGraphTransportFactory::class, 'native' => NativeTransportFactory::class, 'null' => NullTransportFactory::class, 'postal' => PostalTransportFactory::class, diff --git a/Resources/config/profiling.php b/Resources/config/profiling.php index a81c53a63..ba734bee2 100644 --- a/Resources/config/profiling.php +++ b/Resources/config/profiling.php @@ -39,6 +39,7 @@ param('profiler_listener.only_main_requests'), ]) ->tag('kernel.event_subscriber') + ->tag('kernel.reset', ['method' => '?reset']) ->set('console_profiler_listener', ConsoleProfilerListener::class) ->args([ diff --git a/Resources/config/web.php b/Resources/config/web.php index a4e975dac..29e128715 100644 --- a/Resources/config/web.php +++ b/Resources/config/web.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\FrameworkBundle\Controller\ControllerHelper; use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver; use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; @@ -145,6 +146,12 @@ ->set('controller.cache_attribute_listener', CacheAttributeListener::class) ->tag('kernel.event_subscriber') + ->tag('kernel.reset', ['method' => '?reset']) + + ->set('controller.helper', ControllerHelper::class) + ->tag('container.service_subscriber') + + ->alias(ControllerHelper::class, 'controller.helper') ; }; diff --git a/Resources/config/web_link.php b/Resources/config/web_link.php index 64345cc99..df55d1947 100644 --- a/Resources/config/web_link.php +++ b/Resources/config/web_link.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; +use Symfony\Component\WebLink\HttpHeaderParser; use Symfony\Component\WebLink\HttpHeaderSerializer; return static function (ContainerConfigurator $container) { @@ -20,6 +21,9 @@ ->set('web_link.http_header_serializer', HttpHeaderSerializer::class) ->alias(HttpHeaderSerializer::class, 'web_link.http_header_serializer') + ->set('web_link.http_header_parser', HttpHeaderParser::class) + ->alias(HttpHeaderParser::class, 'web_link.http_header_parser') + ->set('web_link.add_link_header_listener', AddLinkHeaderListener::class) ->args([ service('web_link.http_header_serializer'), diff --git a/Routing/Router.php b/Routing/Router.php index 9efa07fae..f9e41273c 100644 --- a/Routing/Router.php +++ b/Routing/Router.php @@ -36,6 +36,9 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberInterface { private array $collectedParameters = []; + /** + * @var \Closure(string):mixed + */ private \Closure $paramFetcher; /** diff --git a/Secrets/SodiumVault.php b/Secrets/SodiumVault.php index 2a8e5dcc8..5abdfdc70 100644 --- a/Secrets/SodiumVault.php +++ b/Secrets/SodiumVault.php @@ -228,7 +228,7 @@ private function export(string $filename, string $data): void private function createSecretsDir(): void { - if ($this->secretsDir && !is_dir($this->secretsDir) && !@mkdir($this->secretsDir, 0777, true) && !is_dir($this->secretsDir)) { + if ($this->secretsDir && !is_dir($this->secretsDir) && !@mkdir($this->secretsDir, 0o777, true) && !is_dir($this->secretsDir)) { throw new \RuntimeException(\sprintf('Unable to create the secrets directory (%s).', $this->secretsDir)); } diff --git a/Test/BrowserKitAssertionsTrait.php b/Test/BrowserKitAssertionsTrait.php index 1b7437b77..6086e75ec 100644 --- a/Test/BrowserKitAssertionsTrait.php +++ b/Test/BrowserKitAssertionsTrait.php @@ -16,6 +16,7 @@ use PHPUnit\Framework\Constraint\LogicalNot; use PHPUnit\Framework\ExpectationFailedException; use Symfony\Component\BrowserKit\AbstractBrowser; +use Symfony\Component\BrowserKit\History; use Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -28,24 +29,31 @@ */ trait BrowserKitAssertionsTrait { - public static function assertResponseIsSuccessful(string $message = '', bool $verbose = true): void + private static bool $defaultVerboseMode = true; + + public static function setBrowserKitAssertionsAsVerbose(bool $verbose): void { - self::assertThatForResponse(new ResponseConstraint\ResponseIsSuccessful($verbose), $message); + self::$defaultVerboseMode = $verbose; } - public static function assertResponseStatusCodeSame(int $expectedCode, string $message = '', bool $verbose = true): void + public static function assertResponseIsSuccessful(string $message = '', ?bool $verbose = null): void { - self::assertThatForResponse(new ResponseConstraint\ResponseStatusCodeSame($expectedCode, $verbose), $message); + self::assertThatForResponse(new ResponseConstraint\ResponseIsSuccessful($verbose ?? self::$defaultVerboseMode), $message); } - public static function assertResponseFormatSame(?string $expectedFormat, string $message = ''): void + public static function assertResponseStatusCodeSame(int $expectedCode, string $message = '', ?bool $verbose = null): void { - self::assertThatForResponse(new ResponseConstraint\ResponseFormatSame(self::getRequest(), $expectedFormat), $message); + self::assertThatForResponse(new ResponseConstraint\ResponseStatusCodeSame($expectedCode, $verbose ?? self::$defaultVerboseMode), $message); } - public static function assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', bool $verbose = true): void + public static function assertResponseFormatSame(?string $expectedFormat, string $message = '', ?bool $verbose = null): void { - $constraint = new ResponseConstraint\ResponseIsRedirected($verbose); + self::assertThatForResponse(new ResponseConstraint\ResponseFormatSame(self::getRequest(), $expectedFormat, $verbose ?? self::$defaultVerboseMode), $message); + } + + public static function assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', ?bool $verbose = null): void + { + $constraint = new ResponseConstraint\ResponseIsRedirected($verbose ?? self::$defaultVerboseMode); if ($expectedLocation) { if (class_exists(ResponseConstraint\ResponseHeaderLocationSame::class)) { $locationConstraint = new ResponseConstraint\ResponseHeaderLocationSame(self::getRequest(), $expectedLocation); @@ -100,9 +108,9 @@ public static function assertResponseCookieValueSame(string $name, string $expec ), $message); } - public static function assertResponseIsUnprocessable(string $message = '', bool $verbose = true): void + public static function assertResponseIsUnprocessable(string $message = '', ?bool $verbose = null): void { - self::assertThatForResponse(new ResponseConstraint\ResponseIsUnprocessable($verbose), $message); + self::assertThatForResponse(new ResponseConstraint\ResponseIsUnprocessable($verbose ?? self::$defaultVerboseMode), $message); } public static function assertBrowserHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void @@ -115,6 +123,38 @@ public static function assertBrowserNotHasCookie(string $name, string $path = '/ self::assertThatForClient(new LogicalNot(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain)), $message); } + public static function assertBrowserHistoryIsOnFirstPage(string $message = ''): void + { + if (!method_exists(History::class, 'isFirstPage')) { + throw new \LogicException('The `assertBrowserHistoryIsOnFirstPage` method requires symfony/browser-kit >= 7.4.'); + } + self::assertThatForClient(new BrowserKitConstraint\BrowserHistoryIsOnFirstPage(), $message); + } + + public static function assertBrowserHistoryIsNotOnFirstPage(string $message = ''): void + { + if (!method_exists(History::class, 'isFirstPage')) { + throw new \LogicException('The `assertBrowserHistoryIsNotOnFirstPage` method requires symfony/browser-kit >= 7.4.'); + } + self::assertThatForClient(new LogicalNot(new BrowserKitConstraint\BrowserHistoryIsOnFirstPage()), $message); + } + + public static function assertBrowserHistoryIsOnLastPage(string $message = ''): void + { + if (!method_exists(History::class, 'isLastPage')) { + throw new \LogicException('The `assertBrowserHistoryIsOnLastPage` method requires symfony/browser-kit >= 7.4.'); + } + self::assertThatForClient(new BrowserKitConstraint\BrowserHistoryIsOnLastPage(), $message); + } + + public static function assertBrowserHistoryIsNotOnLastPage(string $message = ''): void + { + if (!method_exists(History::class, 'isLastPage')) { + throw new \LogicException('The `assertBrowserHistoryIsNotOnLastPage` method requires symfony/browser-kit >= 7.4.'); + } + self::assertThatForClient(new LogicalNot(new BrowserKitConstraint\BrowserHistoryIsOnLastPage()), $message); + } + public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', ?string $domain = null, string $message = ''): void { self::assertThatForClient(LogicalAnd::fromConstraints( diff --git a/Test/MailerAssertionsTrait.php b/Test/MailerAssertionsTrait.php index 2308c3e2f..07f4c99f5 100644 --- a/Test/MailerAssertionsTrait.php +++ b/Test/MailerAssertionsTrait.php @@ -90,6 +90,11 @@ public static function assertEmailAddressContains(RawMessage $email, string $hea self::assertThat($email, new MimeConstraint\EmailAddressContains($headerName, $expectedValue), $message); } + public static function assertEmailAddressNotContains(RawMessage $email, string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThat($email, new LogicalNot(new MimeConstraint\EmailAddressContains($headerName, $expectedValue)), $message); + } + public static function assertEmailSubjectContains(RawMessage $email, string $expectedValue, string $message = ''): void { self::assertThat($email, new MimeConstraint\EmailSubjectContains($expectedValue), $message); diff --git a/Tests/CacheWarmer/SerializerCacheWarmerTest.php b/Tests/CacheWarmer/SerializerCacheWarmerTest.php index f17aad0e3..5c19d2a3f 100644 --- a/Tests/CacheWarmer/SerializerCacheWarmerTest.php +++ b/Tests/CacheWarmer/SerializerCacheWarmerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\CacheWarmer; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Bundle\FrameworkBundle\CacheWarmer\SerializerCacheWarmer; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\Cache\Adapter\NullAdapter; @@ -38,9 +39,7 @@ private function getArrayPool(string $file): PhpArrayAdapter return $this->arrayPool = new PhpArrayAdapter($file, new NullAdapter()); } - /** - * @dataProvider loaderProvider - */ + #[DataProvider('loaderProvider')] public function testWarmUp(array $loaders) { $file = sys_get_temp_dir().'/cache-serializer.php'; @@ -57,9 +56,7 @@ public function testWarmUp(array $loaders) $this->assertTrue($arrayPool->getItem('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author')->isHit()); } - /** - * @dataProvider loaderProvider - */ + #[DataProvider('loaderProvider')] public function testWarmUpAbsoluteFilePath(array $loaders) { $file = sys_get_temp_dir().'/0/cache-serializer.php'; @@ -79,9 +76,7 @@ public function testWarmUpAbsoluteFilePath(array $loaders) $this->assertTrue($arrayPool->getItem('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author')->isHit()); } - /** - * @dataProvider loaderProvider - */ + #[DataProvider('loaderProvider')] public function testWarmUpWithoutBuildDir(array $loaders) { $file = sys_get_temp_dir().'/cache-serializer.php'; diff --git a/Tests/Command/AboutCommand/AboutCommandTest.php b/Tests/Command/AboutCommand/AboutCommandTest.php index bcf3c7fe0..044d816e0 100644 --- a/Tests/Command/AboutCommand/AboutCommandTest.php +++ b/Tests/Command/AboutCommand/AboutCommandTest.php @@ -34,7 +34,7 @@ public function testAboutWithReadableFiles() $this->fs->mkdir($kernel->getProjectDir()); $this->fs->dumpFile($kernel->getCacheDir().'/readable_file', 'The file content.'); - $this->fs->chmod($kernel->getCacheDir().'/readable_file', 0777); + $this->fs->chmod($kernel->getCacheDir().'/readable_file', 0o777); $tester = $this->createCommandTester($kernel); $ret = $tester->execute([]); @@ -43,7 +43,7 @@ public function testAboutWithReadableFiles() $this->assertStringContainsString('Cache directory', $tester->getDisplay()); $this->assertStringContainsString('Log directory', $tester->getDisplay()); - $this->fs->chmod($kernel->getCacheDir().'/readable_file', 0777); + $this->fs->chmod($kernel->getCacheDir().'/readable_file', 0o777); try { $this->fs->remove($kernel->getProjectDir()); @@ -62,7 +62,7 @@ public function testAboutWithUnreadableFiles() } $this->fs->dumpFile($kernel->getCacheDir().'/unreadable_file', 'The file content.'); - $this->fs->chmod($kernel->getCacheDir().'/unreadable_file', 0222); + $this->fs->chmod($kernel->getCacheDir().'/unreadable_file', 0o222); $tester = $this->createCommandTester($kernel); $ret = $tester->execute([]); @@ -71,7 +71,7 @@ public function testAboutWithUnreadableFiles() $this->assertStringContainsString('Cache directory', $tester->getDisplay()); $this->assertStringContainsString('Log directory', $tester->getDisplay()); - $this->fs->chmod($kernel->getCacheDir().'/unreadable_file', 0777); + $this->fs->chmod($kernel->getCacheDir().'/unreadable_file', 0o777); try { $this->fs->remove($kernel->getProjectDir()); @@ -82,7 +82,7 @@ public function testAboutWithUnreadableFiles() private function createCommandTester(TestAppKernel $kernel): CommandTester { $application = new Application($kernel); - $application->add(new AboutCommand()); + $application->addCommand(new AboutCommand()); return new CommandTester($application->find('about')); } diff --git a/Tests/Command/CachePoolClearCommandTest.php b/Tests/Command/CachePoolClearCommandTest.php index 3a927f217..dcf788134 100644 --- a/Tests/Command/CachePoolClearCommandTest.php +++ b/Tests/Command/CachePoolClearCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use Psr\Cache\CacheItemPoolInterface; use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand; @@ -30,13 +31,11 @@ protected function setUp(): void $this->cachePool = $this->createMock(CacheItemPoolInterface::class); } - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(array $input, array $expectedSuggestions) { $application = new Application($this->getKernel()); - $application->add(new CachePoolClearCommand(new Psr6CacheClearer(['foo' => $this->cachePool]), ['foo'])); + $application->addCommand(new CachePoolClearCommand(new Psr6CacheClearer(['foo' => $this->cachePool]), ['foo'])); $tester = new CommandCompletionTester($application->get('cache:pool:clear')); $suggestions = $tester->complete($input); diff --git a/Tests/Command/CachePoolDeleteCommandTest.php b/Tests/Command/CachePoolDeleteCommandTest.php index 3db39e121..afd3ecdd7 100644 --- a/Tests/Command/CachePoolDeleteCommandTest.php +++ b/Tests/Command/CachePoolDeleteCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use Psr\Cache\CacheItemPoolInterface; use Symfony\Bundle\FrameworkBundle\Command\CachePoolDeleteCommand; @@ -84,13 +85,11 @@ public function testCommandDeleteFailed() $tester->execute(['pool' => 'foo', 'key' => 'bar']); } - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(array $input, array $expectedSuggestions) { $application = new Application($this->getKernel()); - $application->add(new CachePoolDeleteCommand(new Psr6CacheClearer(['foo' => $this->cachePool]), ['foo'])); + $application->addCommand(new CachePoolDeleteCommand(new Psr6CacheClearer(['foo' => $this->cachePool]), ['foo'])); $tester = new CommandCompletionTester($application->get('cache:pool:delete')); $suggestions = $tester->complete($input); @@ -125,7 +124,7 @@ private function getKernel(): MockObject&KernelInterface private function getCommandTester(KernelInterface $kernel): CommandTester { $application = new Application($kernel); - $application->add(new CachePoolDeleteCommand(new Psr6CacheClearer(['foo' => $this->cachePool]))); + $application->addCommand(new CachePoolDeleteCommand(new Psr6CacheClearer(['foo' => $this->cachePool]))); return new CommandTester($application->find('cache:pool:delete')); } diff --git a/Tests/Command/CachePruneCommandTest.php b/Tests/Command/CachePruneCommandTest.php index a2d0ad7fe..18a3622f2 100644 --- a/Tests/Command/CachePruneCommandTest.php +++ b/Tests/Command/CachePruneCommandTest.php @@ -77,7 +77,7 @@ private function getPruneableInterfaceMock(): MockObject&PruneableInterface private function getCommandTester(KernelInterface $kernel, RewindableGenerator $generator): CommandTester { $application = new Application($kernel); - $application->add(new CachePoolPruneCommand($generator)); + $application->addCommand(new CachePoolPruneCommand($generator)); return new CommandTester($application->find('cache:pool:prune')); } diff --git a/Tests/Command/EventDispatcherDebugCommandTest.php b/Tests/Command/EventDispatcherDebugCommandTest.php index 359196e11..7dc1e0dc6 100644 --- a/Tests/Command/EventDispatcherDebugCommandTest.php +++ b/Tests/Command/EventDispatcherDebugCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\EventDispatcherDebugCommand; use Symfony\Component\Console\Tester\CommandCompletionTester; @@ -20,9 +21,7 @@ class EventDispatcherDebugCommandTest extends TestCase { - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(array $input, array $expectedSuggestions) { $tester = $this->createCommandCompletionTester(); diff --git a/Tests/Command/RouterMatchCommandTest.php b/Tests/Command/RouterMatchCommandTest.php index b6b6771f9..97b1859be 100644 --- a/Tests/Command/RouterMatchCommandTest.php +++ b/Tests/Command/RouterMatchCommandTest.php @@ -46,8 +46,8 @@ public function testWithNotMatchPath() private function createCommandTester(): CommandTester { $application = new Application($this->getKernel()); - $application->add(new RouterMatchCommand($this->getRouter())); - $application->add(new RouterDebugCommand($this->getRouter())); + $application->addCommand(new RouterMatchCommand($this->getRouter())); + $application->addCommand(new RouterDebugCommand($this->getRouter())); return new CommandTester($application->find('router:match')); } diff --git a/Tests/Command/SecretsDecryptToLocalCommandTest.php b/Tests/Command/SecretsDecryptToLocalCommandTest.php index 8a1c05d69..fb5a66194 100644 --- a/Tests/Command/SecretsDecryptToLocalCommandTest.php +++ b/Tests/Command/SecretsDecryptToLocalCommandTest.php @@ -11,15 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\SecretsDecryptToLocalCommand; use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Filesystem\Filesystem; -/** - * @requires extension sodium - */ +#[RequiresPhpExtension('sodium')] class SecretsDecryptToLocalCommandTest extends TestCase { private string $mainDir; diff --git a/Tests/Command/SecretsEncryptFromLocalCommandTest.php b/Tests/Command/SecretsEncryptFromLocalCommandTest.php index 68926c175..6e458913a 100644 --- a/Tests/Command/SecretsEncryptFromLocalCommandTest.php +++ b/Tests/Command/SecretsEncryptFromLocalCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\SecretsEncryptFromLocalCommand; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; @@ -18,9 +19,7 @@ use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Filesystem\Filesystem; -/** - * @requires extension sodium - */ +#[RequiresPhpExtension('sodium')] class SecretsEncryptFromLocalCommandTest extends TestCase { private string $vaultDir; diff --git a/Tests/Command/SecretsGenerateKeysCommandTest.php b/Tests/Command/SecretsGenerateKeysCommandTest.php index 9574782bf..2940fcfc3 100644 --- a/Tests/Command/SecretsGenerateKeysCommandTest.php +++ b/Tests/Command/SecretsGenerateKeysCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; @@ -18,9 +19,7 @@ use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Filesystem\Filesystem; -/** - * @requires extension sodium - */ +#[RequiresPhpExtension('sodium')] class SecretsGenerateKeysCommandTest extends TestCase { private string $secretsDir; diff --git a/Tests/Command/SecretsListCommandTest.php b/Tests/Command/SecretsListCommandTest.php index 12d3ab2e8..de09d8941 100644 --- a/Tests/Command/SecretsListCommandTest.php +++ b/Tests/Command/SecretsListCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\BackupGlobals; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; @@ -19,9 +20,7 @@ class SecretsListCommandTest extends TestCase { - /** - * @backupGlobals enabled - */ + #[BackupGlobals(true)] public function testExecute() { $vault = $this->createMock(AbstractVault::class); diff --git a/Tests/Command/SecretsRemoveCommandTest.php b/Tests/Command/SecretsRemoveCommandTest.php index 2c12b6128..d09fa3c01 100644 --- a/Tests/Command/SecretsRemoveCommandTest.php +++ b/Tests/Command/SecretsRemoveCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\SecretsRemoveCommand; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; @@ -18,9 +19,7 @@ class SecretsRemoveCommandTest extends TestCase { - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(bool $withLocalVault, array $input, array $expectedSuggestions) { $vault = $this->createMock(AbstractVault::class); diff --git a/Tests/Command/SecretsRevealCommandTest.php b/Tests/Command/SecretsRevealCommandTest.php index d77d303d5..37065d1c0 100644 --- a/Tests/Command/SecretsRevealCommandTest.php +++ b/Tests/Command/SecretsRevealCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\BackupGlobals; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\SecretsRevealCommand; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; @@ -59,9 +60,7 @@ public function testFailedDecrypt() $this->assertStringContainsString('The secret "secretKey" could not be decrypted.', trim($tester->getDisplay(true))); } - /** - * @backupGlobals enabled - */ + #[BackupGlobals(true)] public function testLocalVaultOverride() { $vault = $this->createMock(AbstractVault::class); @@ -78,9 +77,7 @@ public function testLocalVaultOverride() $this->assertEquals('newSecretValue', trim($tester->getDisplay(true))); } - /** - * @backupGlobals enabled - */ + #[BackupGlobals(true)] public function testOnlyLocalVaultContainsName() { $vault = $this->createMock(AbstractVault::class); diff --git a/Tests/Command/SecretsSetCommandTest.php b/Tests/Command/SecretsSetCommandTest.php index 678fb417c..57db9c529 100644 --- a/Tests/Command/SecretsSetCommandTest.php +++ b/Tests/Command/SecretsSetCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; @@ -18,9 +19,7 @@ class SecretsSetCommandTest extends TestCase { - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(array $input, array $expectedSuggestions) { $vault = $this->createMock(AbstractVault::class); diff --git a/Tests/Command/TranslationDebugCommandTest.php b/Tests/Command/TranslationDebugCommandTest.php index c6c91a857..e0e49fd2e 100644 --- a/Tests/Command/TranslationDebugCommandTest.php +++ b/Tests/Command/TranslationDebugCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; @@ -223,7 +224,7 @@ function ($path, $catalogue) use ($loadedMessages) { $command = new TranslationDebugCommand($translator, $loader, $extractor, $this->translationDir.'/translations', $this->translationDir.'/templates', $transPaths, $codePaths, $enabledLocales); $application = new Application($kernel); - $application->add($command); + $application->addCommand($command); return $application->find('debug:translation'); } @@ -240,9 +241,7 @@ private function getBundle($path) return $bundle; } - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(array $input, array $expectedSuggestions) { $extractedMessagesWithDomains = [ diff --git a/Tests/Command/TranslationExtractCommandCompletionTest.php b/Tests/Command/TranslationExtractCommandCompletionTest.php index 6d2f22d96..49874fe7c 100644 --- a/Tests/Command/TranslationExtractCommandCompletionTest.php +++ b/Tests/Command/TranslationExtractCommandCompletionTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\TranslationExtractCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; @@ -30,9 +31,7 @@ class TranslationExtractCommandCompletionTest extends TestCase private Filesystem $fs; private string $translationDir; - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(array $input, array $expectedSuggestions) { $tester = $this->createCommandCompletionTester(['messages' => ['foo' => 'foo']]); @@ -132,7 +131,7 @@ function ($path, $catalogue) use ($loadedMessages) { $command = new TranslationExtractCommand($writer, $loader, $extractor, 'en', $this->translationDir.'/translations', $this->translationDir.'/templates', $transPaths, $codePaths, ['en', 'fr']); $application = new Application($kernel); - $application->add($command); + $application->addCommand($command); return new CommandCompletionTester($application->find('translation:extract')); } diff --git a/Tests/Command/TranslationExtractCommandTest.php b/Tests/Command/TranslationExtractCommandTest.php index c5e78de12..276af4476 100644 --- a/Tests/Command/TranslationExtractCommandTest.php +++ b/Tests/Command/TranslationExtractCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\TranslationExtractCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; @@ -154,12 +155,12 @@ public function testFilterDuplicateTransPaths() if (preg_match('/\.[a-z]+$/', $transPath)) { if (!realpath(\dirname($transPath))) { - mkdir(\dirname($transPath), 0777, true); + mkdir(\dirname($transPath), 0o777, true); } touch($transPath); } else { - mkdir($transPath, 0777, true); + mkdir($transPath, 0o777, true); } } @@ -177,9 +178,7 @@ public function testFilterDuplicateTransPaths() $this->assertEquals($expectedPaths, $filteredTransPaths); } - /** - * @dataProvider removeNoFillProvider - */ + #[DataProvider('removeNoFillProvider')] public function testRemoveNoFillTranslationsMethod($noFillCounter, $messages) { // Preparing mock @@ -304,7 +303,7 @@ function (MessageCatalogue $catalogue) use ($writerMessages) { $command = new TranslationExtractCommand($writer, $loader, $extractor, 'en', $this->translationDir.'/translations', $this->translationDir.'/templates', $transPaths, $codePaths); $application = new Application($kernel); - $application->add($command); + $application->addCommand($command); return new CommandTester($application->find('translation:extract')); } diff --git a/Tests/Command/WorkflowDumpCommandTest.php b/Tests/Command/WorkflowDumpCommandTest.php index 284e97623..d7d17a923 100644 --- a/Tests/Command/WorkflowDumpCommandTest.php +++ b/Tests/Command/WorkflowDumpCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\WorkflowDumpCommand; use Symfony\Component\Console\Application; @@ -19,13 +20,16 @@ class WorkflowDumpCommandTest extends TestCase { - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(array $input, array $expectedSuggestions) { $application = new Application(); - $application->add(new WorkflowDumpCommand(new ServiceLocator([]))); + $command = new WorkflowDumpCommand(new ServiceLocator([])); + if (method_exists($application, 'addCommand')) { + $application->addCommand($command); + } else { + $application->add($command); + } $tester = new CommandCompletionTester($application->find('workflow:dump')); $suggestions = $tester->complete($input, 2); diff --git a/Tests/Command/XliffLintCommandTest.php b/Tests/Command/XliffLintCommandTest.php index d5495ada9..f10834c5b 100644 --- a/Tests/Command/XliffLintCommandTest.php +++ b/Tests/Command/XliffLintCommandTest.php @@ -35,10 +35,10 @@ public function testGetHelp() { $command = new XliffLintCommand(); $expected = <<php %command.full_name% @AcmeDemoBundle -EOF; + php %command.full_name% @AcmeDemoBundle + EOF; $this->assertStringContainsString($expected, $command->getHelp()); } @@ -59,7 +59,12 @@ private function createCommandTester($application = null): CommandTester { if (!$application) { $application = new BaseApplication(); - $application->add(new XliffLintCommand()); + $command = new XliffLintCommand(); + if (method_exists($application, 'addCommand')) { + $application->addCommand($command); + } else { + $application->add($command); + } } $command = $application->find('lint:xliff'); diff --git a/Tests/Command/YamlLintCommandTest.php b/Tests/Command/YamlLintCommandTest.php index ec2093119..28c55a5ba 100644 --- a/Tests/Command/YamlLintCommandTest.php +++ b/Tests/Command/YamlLintCommandTest.php @@ -73,10 +73,10 @@ public function testGetHelp() { $command = new YamlLintCommand(); $expected = <<php %command.full_name% @AcmeDemoBundle -EOF; + php %command.full_name% @AcmeDemoBundle + EOF; $this->assertStringContainsString($expected, $command->getHelp()); } @@ -107,7 +107,12 @@ private function createCommandTester($application = null): CommandTester { if (!$application) { $application = new BaseApplication(); - $application->add(new YamlLintCommand()); + $command = new YamlLintCommand(); + if (method_exists($application, 'addCommand')) { + $application->addCommand($command); + } else { + $application->add($command); + } } $command = $application->find('lint:yaml'); diff --git a/Tests/Console/ApplicationTest.php b/Tests/Console/ApplicationTest.php index 0b92a813c..7f712107c 100644 --- a/Tests/Console/ApplicationTest.php +++ b/Tests/Console/ApplicationTest.php @@ -119,7 +119,7 @@ public function testBundleCommandCanOverriddeAPreExistingCommandWithTheSameName( $application = new Application($kernel); $newCommand = new Command('example'); - $application->add($newCommand); + $application->addCommand($newCommand); $this->assertSame($newCommand, $application->get('example')); } diff --git a/Tests/Console/Descriptor/AbstractDescriptorTestCase.php b/Tests/Console/Descriptor/AbstractDescriptorTestCase.php index eb18fbcc7..a6cbd3085 100644 --- a/Tests/Console/Descriptor/AbstractDescriptorTestCase.php +++ b/Tests/Console/Descriptor/AbstractDescriptorTestCase.php @@ -11,6 +11,9 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\FooUnitEnum; use Symfony\Component\Console\Input\ArrayInput; @@ -39,8 +42,8 @@ protected function tearDown(): void putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS'); } - /** @dataProvider getDescribeRouteCollectionTestData */ - public function testDescribeRouteCollection(RouteCollection $routes, $expectedDescription) + #[DataProvider('getDescribeRouteCollectionTestData')] + public function testDescribeRouteCollection(RouteCollection $routes, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $routes); } @@ -50,8 +53,8 @@ public static function getDescribeRouteCollectionTestData(): array return static::getDescriptionTestData(ObjectsProvider::getRouteCollections()); } - /** @dataProvider getDescribeRouteCollectionWithHttpMethodFilterTestData */ - public function testDescribeRouteCollectionWithHttpMethodFilter(string $httpMethod, RouteCollection $routes, $expectedDescription) + #[DataProvider('getDescribeRouteCollectionWithHttpMethodFilterTestData')] + public function testDescribeRouteCollectionWithHttpMethodFilter(string $httpMethod, RouteCollection $routes, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $routes, ['method' => $httpMethod]); } @@ -65,8 +68,8 @@ public static function getDescribeRouteCollectionWithHttpMethodFilterTestData(): } } - /** @dataProvider getDescribeRouteTestData */ - public function testDescribeRoute(Route $route, $expectedDescription) + #[DataProvider('getDescribeRouteTestData')] + public function testDescribeRoute(Route $route, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $route); } @@ -76,8 +79,8 @@ public static function getDescribeRouteTestData(): array return static::getDescriptionTestData(ObjectsProvider::getRoutes()); } - /** @dataProvider getDescribeContainerParametersTestData */ - public function testDescribeContainerParameters(ParameterBag $parameters, $expectedDescription) + #[DataProvider('getDescribeContainerParametersTestData')] + public function testDescribeContainerParameters(ParameterBag $parameters, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $parameters); } @@ -87,8 +90,8 @@ public static function getDescribeContainerParametersTestData(): array return static::getDescriptionTestData(ObjectsProvider::getContainerParameters()); } - /** @dataProvider getDescribeContainerBuilderTestData */ - public function testDescribeContainerBuilder(ContainerBuilder $builder, $expectedDescription, array $options) + #[DataProvider('getDescribeContainerBuilderTestData')] + public function testDescribeContainerBuilder(ContainerBuilder $builder, $expectedDescription, array $options, $file) { $this->assertDescription($expectedDescription, $builder, $options); } @@ -98,10 +101,8 @@ public static function getDescribeContainerBuilderTestData(): array return static::getContainerBuilderDescriptionTestData(ObjectsProvider::getContainerBuilders()); } - /** - * @dataProvider getDescribeContainerExistingClassDefinitionTestData - */ - public function testDescribeContainerExistingClassDefinition(Definition $definition, $expectedDescription) + #[DataProvider('getDescribeContainerExistingClassDefinitionTestData')] + public function testDescribeContainerExistingClassDefinition(Definition $definition, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $definition); } @@ -111,8 +112,8 @@ public static function getDescribeContainerExistingClassDefinitionTestData(): ar return static::getDescriptionTestData(ObjectsProvider::getContainerDefinitionsWithExistingClasses()); } - /** @dataProvider getDescribeContainerDefinitionTestData */ - public function testDescribeContainerDefinition(Definition $definition, $expectedDescription) + #[DataProvider('getDescribeContainerDefinitionTestData')] + public function testDescribeContainerDefinition(Definition $definition, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $definition); } @@ -122,8 +123,8 @@ public static function getDescribeContainerDefinitionTestData(): array return static::getDescriptionTestData(ObjectsProvider::getContainerDefinitions()); } - /** @dataProvider getDescribeContainerDefinitionWithArgumentsShownTestData */ - public function testDescribeContainerDefinitionWithArgumentsShown(Definition $definition, $expectedDescription) + #[DataProvider('getDescribeContainerDefinitionWithArgumentsShownTestData')] + public function testDescribeContainerDefinitionWithArgumentsShown(Definition $definition, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $definition, []); } @@ -142,8 +143,8 @@ public static function getDescribeContainerDefinitionWithArgumentsShownTestData( return static::getDescriptionTestData($definitionsWithArgs); } - /** @dataProvider getDescribeContainerAliasTestData */ - public function testDescribeContainerAlias(Alias $alias, $expectedDescription) + #[DataProvider('getDescribeContainerAliasTestData')] + public function testDescribeContainerAlias(Alias $alias, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $alias); } @@ -153,8 +154,8 @@ public static function getDescribeContainerAliasTestData(): array return static::getDescriptionTestData(ObjectsProvider::getContainerAliases()); } - /** @dataProvider getDescribeContainerDefinitionWhichIsAnAliasTestData */ - public function testDescribeContainerDefinitionWhichIsAnAlias(Alias $alias, $expectedDescription, ContainerBuilder $builder, $options = []) + #[DataProvider('getDescribeContainerDefinitionWhichIsAnAliasTestData')] + public function testDescribeContainerDefinitionWhichIsAnAlias(Alias $alias, $expectedDescription, ContainerBuilder $builder, $options = [], $file = null) { $this->assertDescription($expectedDescription, $builder, $options); } @@ -185,13 +186,12 @@ public static function getDescribeContainerDefinitionWhichIsAnAliasTestData(): a } /** - * The legacy group must be kept as deprecations will always be raised. - * - * @group legacy - * - * @dataProvider getDescribeContainerParameterTestData + * The #[IgnoreDeprecation] attribute must be kept as deprecations will always be raised. */ - public function testDescribeContainerParameter($parameter, $expectedDescription, array $options) + #[IgnoreDeprecations] + #[Group('legacy')] + #[DataProvider('getDescribeContainerParameterTestData')] + public function testDescribeContainerParameter($parameter, $expectedDescription, array $options, $file) { $this->assertDescription($expectedDescription, $parameter, $options); } @@ -213,8 +213,8 @@ public static function getDescribeContainerParameterTestData(): array return $data; } - /** @dataProvider getDescribeEventDispatcherTestData */ - public function testDescribeEventDispatcher(EventDispatcher $eventDispatcher, $expectedDescription, array $options) + #[DataProvider('getDescribeEventDispatcherTestData')] + public function testDescribeEventDispatcher(EventDispatcher $eventDispatcher, $expectedDescription, array $options, $file) { $this->assertDescription($expectedDescription, $eventDispatcher, $options); } @@ -224,8 +224,8 @@ public static function getDescribeEventDispatcherTestData(): array return static::getEventDispatcherDescriptionTestData(ObjectsProvider::getEventDispatchers()); } - /** @dataProvider getDescribeCallableTestData */ - public function testDescribeCallable($callable, $expectedDescription) + #[DataProvider('getDescribeCallableTestData')] + public function testDescribeCallable($callable, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $callable); } @@ -235,12 +235,10 @@ public static function getDescribeCallableTestData(): array return static::getDescriptionTestData(ObjectsProvider::getCallables()); } - /** - * @group legacy - * - * @dataProvider getDescribeDeprecatedCallableTestData - */ - public function testDescribeDeprecatedCallable($callable, $expectedDescription) + #[IgnoreDeprecations] + #[Group('legacy')] + #[DataProvider('getDescribeDeprecatedCallableTestData')] + public function testDescribeDeprecatedCallable($callable, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $callable); } @@ -250,7 +248,7 @@ public static function getDescribeDeprecatedCallableTestData(): array return static::getDescriptionTestData(ObjectsProvider::getDeprecatedCallables()); } - /** @dataProvider getClassDescriptionTestData */ + #[DataProvider('getClassDescriptionTestData')] public function testGetClassDescription($object, $expectedDescription) { $this->assertEquals($expectedDescription, $this->getDescriptor()->getClassDescription($object)); @@ -266,10 +264,8 @@ public static function getClassDescriptionTestData(): array ]; } - /** - * @dataProvider getDeprecationsTestData - */ - public function testGetDeprecations(ContainerBuilder $builder, $expectedDescription) + #[DataProvider('getDeprecationsTestData')] + public function testGetDeprecations(ContainerBuilder $builder, $expectedDescription, $file) { $this->assertDescription($expectedDescription, $builder, ['deprecations' => true]); } @@ -357,7 +353,7 @@ private static function getEventDispatcherDescriptionTestData(array $objects): a return $data; } - /** @dataProvider getDescribeContainerBuilderWithPriorityTagsTestData */ + #[DataProvider('getDescribeContainerBuilderWithPriorityTagsTestData')] public function testDescribeContainerBuilderWithPriorityTags(ContainerBuilder $builder, $expectedDescription, array $options) { $this->assertDescription($expectedDescription, $builder, $options); diff --git a/Tests/Console/Descriptor/TextDescriptorTest.php b/Tests/Console/Descriptor/TextDescriptorTest.php index 34e16f5e4..101ac68a0 100644 --- a/Tests/Console/Descriptor/TextDescriptorTest.php +++ b/Tests/Console/Descriptor/TextDescriptorTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor; use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; use Symfony\Component\Routing\Route; @@ -45,11 +46,11 @@ public static function getDescribeRouteWithControllerLinkTestData() return $getDescribeData; } - /** @dataProvider getDescribeRouteWithControllerLinkTestData */ - public function testDescribeRouteWithControllerLink(Route $route, $expectedDescription) + #[DataProvider('getDescribeRouteWithControllerLinkTestData')] + public function testDescribeRouteWithControllerLink(Route $route, $expectedDescription, $file) { static::$fileLinkFormatter = new FileLinkFormatter('myeditor://open?file=%f&line=%l'); - parent::testDescribeRoute($route, str_replace('[:file:]', __FILE__, $expectedDescription)); + parent::testDescribeRoute($route, str_replace('[:file:]', __FILE__, $expectedDescription), $file); } } diff --git a/Tests/Controller/AbstractControllerTest.php b/Tests/Controller/AbstractControllerTest.php index 2024cb8f7..f778aa4be 100644 --- a/Tests/Controller/AbstractControllerTest.php +++ b/Tests/Controller/AbstractControllerTest.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Controller; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\DependencyInjection\Container; @@ -101,7 +103,7 @@ public function testMissingParameterBag() $controller->setContainer($container); $this->expectException(ServiceNotFoundException::class); - $this->expectExceptionMessage('TestAbstractController::getParameter()" method is missing a parameter bag'); + $this->expectExceptionMessage('::getParameter()" method is missing a parameter bag'); $controller->getParameter('foo'); } @@ -389,9 +391,7 @@ public function testdenyAccessUnlessGranted() } } - /** - * @dataProvider provideDenyAccessUnlessGrantedSetsAttributesAsArray - */ + #[DataProvider('provideDenyAccessUnlessGrantedSetsAttributesAsArray')] public function testdenyAccessUnlessGrantedSetsAttributesAsArray($attribute, $exceptionAttributes) { $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); @@ -526,9 +526,7 @@ public function testRedirectToRoute() $this->assertSame(302, $response->getStatusCode()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testAddFlash() { $flashBag = new FlashBag(); diff --git a/Tests/Controller/ControllerHelperTest.php b/Tests/Controller/ControllerHelperTest.php new file mode 100644 index 000000000..cb35c4757 --- /dev/null +++ b/Tests/Controller/ControllerHelperTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Controller; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\FrameworkBundle\Controller\ControllerHelper; + +class ControllerHelperTest extends AbstractControllerTest +{ + protected function createController() + { + return new class extends ControllerHelper { + public function __construct() + { + } + + public function setContainer(ContainerInterface $container) + { + parent::__construct($container); + } + }; + } + + public function testSync() + { + $r = new \ReflectionClass(ControllerHelper::class); + $m = $r->getMethod('getSubscribedServices'); + $helperSrc = file($r->getFileName()); + $helperSrc = implode('', \array_slice($helperSrc, $m->getStartLine() - 1, $r->getEndLine() - $m->getStartLine())); + + $r = new \ReflectionClass(AbstractController::class); + $m = $r->getMethod('getSubscribedServices'); + $abstractSrc = file($r->getFileName()); + $code = [ + implode('', \array_slice($abstractSrc, $m->getStartLine() - 1, $m->getEndLine() - $m->getStartLine() + 1)), + ]; + + foreach ($r->getMethods(\ReflectionMethod::IS_PROTECTED) as $m) { + if ($m->getDocComment()) { + $code[] = ' '.$m->getDocComment(); + } + $code[] = substr_replace(implode('', \array_slice($abstractSrc, $m->getStartLine() - 1, $m->getEndLine() - $m->getStartLine() + 1)), 'public', 4, 9); + } + foreach ($r->getMethods(\ReflectionMethod::IS_PRIVATE) as $m) { + if ($m->getDocComment()) { + $code[] = ' '.$m->getDocComment(); + } + $code[] = implode('', \array_slice($abstractSrc, $m->getStartLine() - 1, $m->getEndLine() - $m->getStartLine() + 1)); + } + $code = implode("\n", $code); + + $this->assertSame($code, $helperSrc, 'Methods from AbstractController are not properly synced in ControllerHelper'); + } +} diff --git a/Tests/Controller/RedirectControllerTest.php b/Tests/Controller/RedirectControllerTest.php index 161424e0e..55f3b33c3 100644 --- a/Tests/Controller/RedirectControllerTest.php +++ b/Tests/Controller/RedirectControllerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Controller; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\HttpFoundation\ParameterBag; @@ -60,9 +61,7 @@ public function testEmptyRoute() } } - /** - * @dataProvider provider - */ + #[DataProvider('provider')] public function testRoute($permanent, $keepRequestMethod, $keepQueryParams, $ignoreAttributes, $expectedCode, $expectedAttributes) { $request = new Request(); @@ -255,9 +254,7 @@ public static function urlRedirectProvider(): array ]; } - /** - * @dataProvider urlRedirectProvider - */ + #[DataProvider('urlRedirectProvider')] public function testUrlRedirect($scheme, $httpPort, $httpsPort, $requestScheme, $requestPort, $expectedPort) { $host = 'www.example.com'; @@ -287,9 +284,7 @@ public static function pathQueryParamsProvider(): array ]; } - /** - * @dataProvider pathQueryParamsProvider - */ + #[DataProvider('pathQueryParamsProvider')] public function testPathQueryParams($expectedUrl, $path, $queryString) { $scheme = 'http'; diff --git a/Tests/DependencyInjection/Compiler/ProfilerPassTest.php b/Tests/DependencyInjection/Compiler/ProfilerPassTest.php index 5a2215009..12dfc085d 100644 --- a/Tests/DependencyInjection/Compiler/ProfilerPassTest.php +++ b/Tests/DependencyInjection/Compiler/ProfilerPassTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector; use Symfony\Bundle\FrameworkBundle\DataCollector\TemplateAwareDataCollectorInterface; @@ -98,9 +99,7 @@ public static function getTemplate(): string }]; } - /** - * @dataProvider provideValidCollectorWithTemplateUsingAutoconfigure - */ + #[DataProvider('provideValidCollectorWithTemplateUsingAutoconfigure')] public function testValidCollectorWithTemplateUsingAutoconfigure(TemplateAwareDataCollectorInterface $dataCollector) { $container = new ContainerBuilder(); diff --git a/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php b/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php index fc69d5bd1..3a6824e4f 100644 --- a/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php +++ b/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php @@ -33,44 +33,44 @@ public function testProcess() $container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32); $container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING); - $container->register('Test\public_service') + $container->register('test.public_service', 'stdClass') ->setPublic(true) - ->addArgument(new Reference('Test\private_used_shared_service')) - ->addArgument(new Reference('Test\private_used_non_shared_service')) - ->addArgument(new Reference('Test\soon_private_service')) + ->addArgument(new Reference('test.private_used_shared_service')) + ->addArgument(new Reference('test.private_used_non_shared_service')) + ->addArgument(new Reference('test.soon_private_service')) ; - $container->register('Test\soon_private_service') + $container->register('test.soon_private_service', 'stdClass') ->setPublic(true) ->addTag('container.private', ['package' => 'foo/bar', 'version' => '1.42']) ; - $container->register('Test\soon_private_service_decorated') + $container->register('test.soon_private_service_decorated', 'stdClass') ->setPublic(true) ->addTag('container.private', ['package' => 'foo/bar', 'version' => '1.42']) ; - $container->register('Test\soon_private_service_decorator') - ->setDecoratedService('Test\soon_private_service_decorated') - ->setArguments(['Test\soon_private_service_decorator.inner']); + $container->register('test.soon_private_service_decorator', 'stdClass') + ->setDecoratedService('test.soon_private_service_decorated') + ->setArguments(['test.soon_private_service_decorator.inner']); - $container->register('Test\private_used_shared_service'); - $container->register('Test\private_unused_shared_service'); - $container->register('Test\private_used_non_shared_service')->setShared(false); - $container->register('Test\private_unused_non_shared_service')->setShared(false); + $container->register('test.private_used_shared_service', 'stdClass'); + $container->register('test.private_unused_shared_service', 'stdClass'); + $container->register('test.private_used_non_shared_service', 'stdClass')->setShared(false); + $container->register('test.private_unused_non_shared_service', 'stdClass')->setShared(false); $container->compile(); $expected = [ - 'Test\private_used_shared_service' => new ServiceClosureArgument(new Reference('Test\private_used_shared_service')), - 'Test\private_used_non_shared_service' => new ServiceClosureArgument(new Reference('Test\private_used_non_shared_service')), - 'Test\soon_private_service' => new ServiceClosureArgument(new Reference('.container.private.Test\soon_private_service')), - 'Test\soon_private_service_decorator' => new ServiceClosureArgument(new Reference('.container.private.Test\soon_private_service_decorated')), - 'Test\soon_private_service_decorated' => new ServiceClosureArgument(new Reference('.container.private.Test\soon_private_service_decorated')), + 'test.private_used_shared_service' => new ServiceClosureArgument(new Reference('test.private_used_shared_service')), + 'test.private_used_non_shared_service' => new ServiceClosureArgument(new Reference('test.private_used_non_shared_service')), + 'test.soon_private_service' => new ServiceClosureArgument(new Reference('.container.private.test.soon_private_service')), + 'test.soon_private_service_decorator' => new ServiceClosureArgument(new Reference('.container.private.test.soon_private_service_decorated')), + 'test.soon_private_service_decorated' => new ServiceClosureArgument(new Reference('.container.private.test.soon_private_service_decorated')), ]; $privateServices = $container->getDefinition('test.private_services_locator')->getArgument(0); 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()); + $this->assertFalse($container->getDefinition('test.private_used_non_shared_service')->isShared()); } } diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index c8142e98a..9bf8d5593 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; use Doctrine\DBAL\Connection; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Configuration; use Symfony\Bundle\FullStack; @@ -61,9 +62,7 @@ public function getTestValidSessionName() ]; } - /** - * @dataProvider getTestInvalidSessionName - */ + #[DataProvider('getTestInvalidSessionName')] public function testInvalidSessionName($sessionName) { $processor = new Processor(); @@ -153,9 +152,7 @@ public function testAssetMapperCanBeEnabled() $this->assertEquals($defaultConfig, $config['asset_mapper']); } - /** - * @dataProvider provideImportmapPolyfillTests - */ + #[DataProvider('provideImportmapPolyfillTests')] public function testAssetMapperPolyfillValue(mixed $polyfillValue, bool $isValid, mixed $expected) { $processor = new Processor(); @@ -189,9 +186,7 @@ public static function provideImportmapPolyfillTests() yield [false, true, false]; } - /** - * @dataProvider provideValidAssetsPackageNameConfigurationTests - */ + #[DataProvider('provideValidAssetsPackageNameConfigurationTests')] public function testValidAssetsPackageNameConfiguration($packageName) { $processor = new Processor(); @@ -221,9 +216,7 @@ public static function provideValidAssetsPackageNameConfigurationTests(): array ]; } - /** - * @dataProvider provideInvalidAssetConfigurationTests - */ + #[DataProvider('provideInvalidAssetConfigurationTests')] public function testInvalidAssetsConfiguration(array $assetConfig, $expectedMessage) { $processor = new Processor(); @@ -275,9 +268,7 @@ public static function provideInvalidAssetConfigurationTests(): iterable yield [$createPackageConfig($config), 'You cannot use both "version" and "json_manifest_path" at the same time under "assets" packages.']; } - /** - * @dataProvider provideValidLockConfigurationTests - */ + #[DataProvider('provideValidLockConfigurationTests')] public function testValidLockConfiguration($lockConfig, $processedConfig) { $processor = new Processor(); @@ -375,9 +366,7 @@ public function testLockMergeConfigs() ); } - /** - * @dataProvider provideValidSemaphoreConfigurationTests - */ + #[DataProvider('provideValidSemaphoreConfigurationTests')] public function testValidSemaphoreConfiguration($semaphoreConfig, $processedConfig) { $processor = new Processor(); diff --git a/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 91a2d9198..ba9732239 100644 --- a/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -11,8 +11,10 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; +use PHPUnit\Framework\Attributes\DataProvider; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerAwareInterface; +use Psr\Log\LogLevel; use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Validator\DefinitionValidator; @@ -58,6 +60,10 @@ use Symfony\Component\HttpClient\ThrottlingHttpClient; use Symfony\Component\HttpFoundation\IpUtils; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; use Symfony\Component\Lock\Store\SemaphoreStore; use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; @@ -598,8 +604,8 @@ public function testPhpErrorsWithLogLevels() $definition = $container->getDefinition('debug.error_handler_configurator'); $this->assertEquals(new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(0)); $this->assertSame([ - \E_NOTICE => \Psr\Log\LogLevel::ERROR, - \E_WARNING => \Psr\Log\LogLevel::ERROR, + \E_NOTICE => LogLevel::ERROR, + \E_WARNING => LogLevel::ERROR, ], $definition->getArgument(1)); } @@ -610,35 +616,35 @@ public function testExceptionsConfig() $configuration = $container->getDefinition('exception_listener')->getArgument(3); $this->assertSame([ - \Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class, - \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class, - \Symfony\Component\HttpKernel\Exception\ConflictHttpException::class, - \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class, + BadRequestHttpException::class, + NotFoundHttpException::class, + ConflictHttpException::class, + ServiceUnavailableHttpException::class, ], array_keys($configuration)); $this->assertEqualsCanonicalizing([ 'log_channel' => null, 'log_level' => 'info', 'status_code' => 422, - ], $configuration[\Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class]); + ], $configuration[BadRequestHttpException::class]); $this->assertEqualsCanonicalizing([ 'log_channel' => null, 'log_level' => 'info', 'status_code' => null, - ], $configuration[\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class]); + ], $configuration[NotFoundHttpException::class]); $this->assertEqualsCanonicalizing([ 'log_channel' => null, 'log_level' => 'info', 'status_code' => null, - ], $configuration[\Symfony\Component\HttpKernel\Exception\ConflictHttpException::class]); + ], $configuration[ConflictHttpException::class]); $this->assertEqualsCanonicalizing([ 'log_channel' => null, 'log_level' => null, 'status_code' => 500, - ], $configuration[\Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class]); + ], $configuration[ServiceUnavailableHttpException::class]); } public function testRouter() @@ -1934,9 +1940,7 @@ public function testRedisTagAwareAdapter() } } - /** - * @dataProvider appRedisTagAwareConfigProvider - */ + #[DataProvider('appRedisTagAwareConfigProvider')] public function testAppRedisTagAwareAdapter(string $configFile) { $container = $this->createContainerFromFile($configFile); @@ -1980,9 +1984,7 @@ public function testCacheTaggableTagAppliedToPools() } } - /** - * @dataProvider appRedisTagAwareConfigProvider - */ + #[DataProvider('appRedisTagAwareConfigProvider')] public function testCacheTaggableTagAppliedToRedisAwareAppPool(string $configFile) { $container = $this->createContainerFromFile($configFile); @@ -2222,9 +2224,7 @@ public static function provideMailer(): iterable ]; } - /** - * @dataProvider provideMailer - */ + #[DataProvider('provideMailer')] public function testMailer(string $configFile, array $expectedTransports, array $expectedRecipients, array $expectedAllowedRecipients) { $container = $this->createContainerFromFile($configFile); diff --git a/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/Tests/DependencyInjection/PhpFrameworkExtensionTest.php index c4f67c2f1..d3c1f8ef4 100644 --- a/Tests/DependencyInjection/PhpFrameworkExtensionTest.php +++ b/Tests/DependencyInjection/PhpFrameworkExtensionTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -135,9 +136,7 @@ public function testWorkflowValidationStateMachine() }); } - /** - * @dataProvider provideWorkflowValidationCustomTests - */ + #[DataProvider('provideWorkflowValidationCustomTests')] public function testWorkflowValidationCustomBroken(string $class, string $message) { $this->expectException(InvalidConfigurationException::class); @@ -431,9 +430,7 @@ public function testRateLimiterCompoundPolicyInvalidLimiters() }); } - /** - * @dataProvider emailValidationModeProvider - */ + #[DataProvider('emailValidationModeProvider')] public function testValidatorEmailValidationMode(string $mode) { $this->expectNotToPerformAssertions(); diff --git a/Tests/Fixtures/Descriptor/route_1_link.txt b/Tests/Fixtures/Descriptor/route_1_link.txt index ad7a4c8c8..b44fb4dbd 100644 --- a/Tests/Fixtures/Descriptor/route_1_link.txt +++ b/Tests/Fixtures/Descriptor/route_1_link.txt @@ -10,7 +10,7 @@ | Method | GET|HEAD | | Requirements | name: [a-z]+ | | Class | Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub | -| Defaults | _controller: ]8;;myeditor://open?file=[:file:]&line=58\Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\MyController::__invoke()]8;;\ | +| Defaults | _controller: ]8;;myeditor://open?file=[:file:]&line=59\Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\MyController::__invoke()]8;;\ | | | name: Joseph | | Options | compiler_class: Symfony\Component\Routing\RouteCompiler | | | opt1: val1 | diff --git a/Tests/Fixtures/Descriptor/route_2_link.txt b/Tests/Fixtures/Descriptor/route_2_link.txt index 8e3fe4ca7..f033787a7 100644 --- a/Tests/Fixtures/Descriptor/route_2_link.txt +++ b/Tests/Fixtures/Descriptor/route_2_link.txt @@ -10,7 +10,7 @@ | Method | PUT|POST | | Requirements | NO CUSTOM | | Class | Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub | -| Defaults | _controller: ]8;;myeditor://open?file=[:file:]&line=58\Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\MyController::__invoke()]8;;\ | +| Defaults | _controller: ]8;;myeditor://open?file=[:file:]&line=59\Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\MyController::__invoke()]8;;\ | | Options | compiler_class: Symfony\Component\Routing\RouteCompiler | | | opt1: val1 | | | opt2: val2 | diff --git a/Tests/Functional/AbstractAttributeRoutingTestCase.php b/Tests/Functional/AbstractAttributeRoutingTestCase.php index 5166c8dda..842d7268f 100644 --- a/Tests/Functional/AbstractAttributeRoutingTestCase.php +++ b/Tests/Functional/AbstractAttributeRoutingTestCase.php @@ -11,13 +11,12 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\HttpFoundation\Request; abstract class AbstractAttributeRoutingTestCase extends AbstractWebTestCase { - /** - * @dataProvider getRoutes - */ + #[DataProvider('getRoutes')] public function testAnnotatedController(string $path, string $expectedValue) { $client = $this->createClient(['test_case' => $this->getTestCaseApp(), 'root_config' => 'config.yml']); diff --git a/Tests/Functional/ApiAttributesTest.php b/Tests/Functional/ApiAttributesTest.php index 4848976ae..79c8a704b 100644 --- a/Tests/Functional/ApiAttributesTest.php +++ b/Tests/Functional/ApiAttributesTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -21,9 +22,7 @@ class ApiAttributesTest extends AbstractWebTestCase { - /** - * @dataProvider mapQueryStringProvider - */ + #[DataProvider('mapQueryStringProvider')] public function testMapQueryString(string $uri, array $query, string $expectedResponse, int $expectedStatusCode) { $client = self::createClient(['test_case' => 'ApiAttributesTest']); @@ -146,24 +145,24 @@ public static function mapQueryStringProvider(): iterable ]; $expectedResponse = <<<'JSON' - { - "type": "https:\/\/symfony.com\/errors\/validation", - "title": "Validation Failed", - "status": 404, - "detail": "filter: This value should be of type Symfony\\Bundle\\FrameworkBundle\\Tests\\Functional\\Filter.", - "violations": [ - { - "parameters": { - "hint": "Failed to create object because the class misses the \"filter\" property.", - "{{ type }}": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Functional\\Filter" - }, - "propertyPath": "filter", - "template": "This value should be of type {{ type }}.", - "title": "This value should be of type Symfony\\Bundle\\FrameworkBundle\\Tests\\Functional\\Filter." - } - ] - } - JSON; + { + "type": "https:\/\/symfony.com\/errors\/validation", + "title": "Validation Failed", + "status": 404, + "detail": "filter: This value should be of type Symfony\\Bundle\\FrameworkBundle\\Tests\\Functional\\Filter.", + "violations": [ + { + "parameters": { + "hint": "Failed to create object because the class misses the \"filter\" property.", + "{{ type }}": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Functional\\Filter" + }, + "propertyPath": "filter", + "template": "This value should be of type {{ type }}.", + "title": "This value should be of type Symfony\\Bundle\\FrameworkBundle\\Tests\\Functional\\Filter." + } + ] + } + JSON; yield 'empty query string mapping non-nullable attribute without default value' => [ 'uri' => '/map-query-string-to-non-nullable-attribute-without-default-value.json', @@ -214,9 +213,7 @@ public static function mapQueryStringProvider(): iterable ]; } - /** - * @dataProvider mapRequestPayloadProvider - */ + #[DataProvider('mapRequestPayloadProvider')] public function testMapRequestPayload(string $uri, string $format, array $parameters, ?string $content, callable $responseAssertion, int $expectedStatusCode) { $client = self::createClient(['test_case' => 'ApiAttributesTest']); @@ -603,7 +600,7 @@ public static function mapRequestPayloadProvider(): iterable self::assertIsArray($json['violations'] ?? null); self::assertCount(1, $json['violations']); self::assertSame('approved', $json['violations'][0]['propertyPath'] ?? null); -}, + }, 'expectedStatusCode' => 422, ]; @@ -947,11 +944,11 @@ public function __invoke(#[MapRequestPayload] ?RequestBody $body, Request $reque return new Response( << - {$body->comment} - {$body->approved} - - XML + + {$body->comment} + {$body->approved} + + XML ); } } @@ -966,11 +963,11 @@ public function __invoke(Request $request, #[MapRequestPayload] RequestBody $bod return new Response( << - {$body->comment} - {$body->approved} - - XML + + {$body->comment} + {$body->approved} + + XML ); } } @@ -985,11 +982,11 @@ public function __invoke(Request $request, #[MapRequestPayload] RequestBody $bod return new Response( << - {$body->comment} - {$body->approved} - - XML + + {$body->comment} + {$body->approved} + + XML ); } } diff --git a/Tests/Functional/BundlePathsTest.php b/Tests/Functional/BundlePathsTest.php index a06803434..45663f0bf 100644 --- a/Tests/Functional/BundlePathsTest.php +++ b/Tests/Functional/BundlePathsTest.php @@ -28,7 +28,7 @@ public function testBundlePublicDir() $fs = new Filesystem(); $fs->remove($projectDir); $fs->mkdir($projectDir.'/public'); - $command = (new Application($kernel))->add(new AssetsInstallCommand($fs, $projectDir)); + $command = (new Application($kernel))->addCommand(new AssetsInstallCommand($fs, $projectDir)); $exitCode = (new CommandTester($command))->execute(['target' => $projectDir.'/public']); $this->assertSame(0, $exitCode); diff --git a/Tests/Functional/CachePoolClearCommandTest.php b/Tests/Functional/CachePoolClearCommandTest.php index dbd78645d..53e8b5c48 100644 --- a/Tests/Functional/CachePoolClearCommandTest.php +++ b/Tests/Functional/CachePoolClearCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\Group; use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Cache\Adapter\FilesystemAdapter; @@ -19,9 +20,7 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\Finder\SplFileInfo; -/** - * @group functional - */ +#[Group('functional')] class CachePoolClearCommandTest extends AbstractWebTestCase { protected function setUp(): void @@ -146,7 +145,7 @@ public function testExcludedPool() private function createCommandTester(?array $poolNames = null) { $application = new Application(static::$kernel); - $application->add(new CachePoolClearCommand(static::getContainer()->get('cache.global_clearer'), $poolNames)); + $application->addCommand(new CachePoolClearCommand(static::getContainer()->get('cache.global_clearer'), $poolNames)); return new CommandTester($application->find('cache:pool:clear')); } diff --git a/Tests/Functional/CachePoolListCommandTest.php b/Tests/Functional/CachePoolListCommandTest.php index 8e9061845..6dcbc4294 100644 --- a/Tests/Functional/CachePoolListCommandTest.php +++ b/Tests/Functional/CachePoolListCommandTest.php @@ -11,13 +11,12 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\Group; use Symfony\Bundle\FrameworkBundle\Command\CachePoolListCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandTester; -/** - * @group functional - */ +#[Group('functional')] class CachePoolListCommandTest extends AbstractWebTestCase { protected function setUp(): void @@ -46,7 +45,7 @@ public function testEmptyList() private function createCommandTester(array $poolNames) { $application = new Application(static::$kernel); - $application->add(new CachePoolListCommand($poolNames)); + $application->addCommand(new CachePoolListCommand($poolNames)); return new CommandTester($application->find('cache:pool:list')); } diff --git a/Tests/Functional/CachePoolsTest.php b/Tests/Functional/CachePoolsTest.php index 23f4a116e..64829949a 100644 --- a/Tests/Functional/CachePoolsTest.php +++ b/Tests/Functional/CachePoolsTest.php @@ -11,6 +11,9 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\Error\Warning; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\RedisAdapter; use Symfony\Component\Cache\Adapter\TagAwareAdapter; @@ -24,18 +27,15 @@ public function testCachePools() $this->doTestCachePools([], AdapterInterface::class); } - /** - * @requires extension redis - * - * @group integration - */ + #[RequiresPhpExtension('redis')] + #[Group('integration')] public function testRedisCachePools() { $this->skipIfRedisUnavailable(); try { $this->doTestCachePools(['root_config' => 'redis_config.yml', 'environment' => 'redis_cache'], RedisAdapter::class); - } catch (\PHPUnit\Framework\Error\Warning $e) { + } catch (Warning $e) { if (!str_starts_with($e->getMessage(), 'unable to connect to')) { throw $e; } @@ -48,18 +48,15 @@ public function testRedisCachePools() } } - /** - * @requires extension redis - * - * @group integration - */ + #[RequiresPhpExtension('redis')] + #[Group('integration')] public function testRedisCustomCachePools() { $this->skipIfRedisUnavailable(); try { $this->doTestCachePools(['root_config' => 'redis_custom_config.yml', 'environment' => 'custom_redis_cache'], RedisAdapter::class); - } catch (\PHPUnit\Framework\Error\Warning $e) { + } catch (Warning $e) { if (!str_starts_with($e->getMessage(), 'unable to connect to')) { throw $e; } diff --git a/Tests/Functional/ConfigDebugCommandTest.php b/Tests/Functional/ConfigDebugCommandTest.php index 2c47121c1..ea4ee0788 100644 --- a/Tests/Functional/ConfigDebugCommandTest.php +++ b/Tests/Functional/ConfigDebugCommandTest.php @@ -11,6 +11,9 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\TestWith; use Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Exception\InvalidArgumentException; @@ -19,15 +22,11 @@ use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; -/** - * @group functional - */ +#[Group('functional')] class ConfigDebugCommandTest extends AbstractWebTestCase { - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testShowList(bool $debug) { $tester = $this->createCommandTester($debug); @@ -44,10 +43,8 @@ public function testShowList(bool $debug) $this->assertStringContainsString(' test_dump', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpKernelExtension(bool $debug) { $tester = $this->createCommandTester($debug); @@ -58,10 +55,8 @@ public function testDumpKernelExtension(bool $debug) $this->assertStringContainsString(' foo: bar', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpBundleName(bool $debug) { $tester = $this->createCommandTester($debug); @@ -71,10 +66,8 @@ public function testDumpBundleName(bool $debug) $this->assertStringContainsString('custom: foo', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpBundleOption(bool $debug) { $tester = $this->createCommandTester($debug); @@ -84,10 +77,8 @@ public function testDumpBundleOption(bool $debug) $this->assertStringContainsString('foo', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpWithoutTitleIsValidJson(bool $debug) { $tester = $this->createCommandTester($debug); @@ -97,10 +88,8 @@ public function testDumpWithoutTitleIsValidJson(bool $debug) $this->assertJson($tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpWithUnsupportedFormat(bool $debug) { $tester = $this->createCommandTester($debug); @@ -114,10 +103,8 @@ public function testDumpWithUnsupportedFormat(bool $debug) ]); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testParametersValuesAreResolved(bool $debug) { $tester = $this->createCommandTester($debug); @@ -128,10 +115,8 @@ public function testParametersValuesAreResolved(bool $debug) $this->assertStringContainsString('secret: test', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testParametersValuesAreFullyResolved(bool $debug) { $tester = $this->createCommandTester($debug); @@ -144,10 +129,8 @@ public function testParametersValuesAreFullyResolved(bool $debug) $this->assertStringContainsString('ide: '.($debug ? ($_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? 'null') : 'null'), $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDefaultParameterValueIsResolvedIfConfigIsExisting(bool $debug) { $tester = $this->createCommandTester($debug); @@ -158,10 +141,8 @@ public function testDefaultParameterValueIsResolvedIfConfigIsExisting(bool $debu $this->assertStringContainsString(\sprintf("dsn: 'file:%s/profiler'", $kernelCacheDir), $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpExtensionConfigWithoutBundle(bool $debug) { $tester = $this->createCommandTester($debug); @@ -171,10 +152,8 @@ public function testDumpExtensionConfigWithoutBundle(bool $debug) $this->assertStringContainsString('enabled: true', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpUndefinedBundleOption(bool $debug) { $tester = $this->createCommandTester($debug); @@ -183,10 +162,8 @@ public function testDumpUndefinedBundleOption(bool $debug) $this->assertStringContainsString('Unable to find configuration for "test.foo"', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpWithPrefixedEnv(bool $debug) { $tester = $this->createCommandTester($debug); @@ -195,10 +172,8 @@ public function testDumpWithPrefixedEnv(bool $debug) $this->assertStringContainsString("cookie_httponly: '%env(bool:COOKIE_HTTPONLY)%'", $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpFallsBackToDefaultConfigAndResolvesParameterValue(bool $debug) { $tester = $this->createCommandTester($debug); @@ -208,10 +183,8 @@ public function testDumpFallsBackToDefaultConfigAndResolvesParameterValue(bool $ $this->assertStringContainsString('foo: bar', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpFallsBackToDefaultConfigAndResolvesEnvPlaceholder(bool $debug) { $tester = $this->createCommandTester($debug); @@ -221,10 +194,8 @@ public function testDumpFallsBackToDefaultConfigAndResolvesEnvPlaceholder(bool $ $this->assertStringContainsString("baz: '%env(BAZ)%'", $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpThrowsExceptionWhenDefaultConfigFallbackIsImpossible(bool $debug) { $this->expectException(\LogicException::class); @@ -234,14 +205,12 @@ public function testDumpThrowsExceptionWhenDefaultConfigFallbackIsImpossible(boo $tester->execute(['name' => 'ExtensionWithoutConfigTestBundle']); } - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(bool $debug, array $input, array $expectedSuggestions) { $application = $this->createApplication($debug); - $application->add(new ConfigDebugCommand()); + $application->addCommand(new ConfigDebugCommand()); $tester = new CommandCompletionTester($application->get('debug:config')); $suggestions = $tester->complete($input); diff --git a/Tests/Functional/ConfigDumpReferenceCommandTest.php b/Tests/Functional/ConfigDumpReferenceCommandTest.php index 8f5930faa..377a530e9 100644 --- a/Tests/Functional/ConfigDumpReferenceCommandTest.php +++ b/Tests/Functional/ConfigDumpReferenceCommandTest.php @@ -11,6 +11,9 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\TestWith; use Symfony\Bundle\FrameworkBundle\Command\ConfigDumpReferenceCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArrayInput; @@ -18,15 +21,11 @@ use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; -/** - * @group functional - */ +#[Group('functional')] class ConfigDumpReferenceCommandTest extends AbstractWebTestCase { - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testShowList(bool $debug) { $tester = $this->createCommandTester($debug); @@ -43,10 +42,8 @@ public function testShowList(bool $debug) $this->assertStringContainsString(' test_dump', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpKernelExtension(bool $debug) { $tester = $this->createCommandTester($debug); @@ -57,10 +54,8 @@ public function testDumpKernelExtension(bool $debug) $this->assertStringContainsString(' bar', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpBundleName(bool $debug) { $tester = $this->createCommandTester($debug); @@ -71,10 +66,8 @@ public function testDumpBundleName(bool $debug) $this->assertStringContainsString(' custom:', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpExtensionConfigWithoutBundle(bool $debug) { $tester = $this->createCommandTester($debug); @@ -84,10 +77,8 @@ public function testDumpExtensionConfigWithoutBundle(bool $debug) $this->assertStringContainsString('enabled: true', $tester->getDisplay()); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpAtPath(bool $debug) { $tester = $this->createCommandTester($debug); @@ -98,20 +89,19 @@ public function testDumpAtPath(bool $debug) $this->assertSame(0, $ret, 'Returns 0 in case of success'); $this->assertSame(<<<'EOL' -# Default configuration for extension with alias: "test" at path "array" -array: - child1: ~ - child2: ~ + # Default configuration for extension with alias: "test" at path "array" + array: + child1: ~ + child2: ~ -EOL - , $tester->getDisplay(true)); + EOL, + $tester->getDisplay(true) + ); } - /** - * @testWith [true] - * [false] - */ + #[TestWith([true])] + #[TestWith([false])] public function testDumpAtPathXml(bool $debug) { $tester = $this->createCommandTester($debug); @@ -125,14 +115,12 @@ public function testDumpAtPathXml(bool $debug) $this->assertStringContainsString('[ERROR] The "path" option is only available for the "yaml" format.', $tester->getDisplay()); } - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(bool $debug, array $input, array $expectedSuggestions) { $application = $this->createApplication($debug); - $application->add(new ConfigDumpReferenceCommand()); + $application->addCommand(new ConfigDumpReferenceCommand()); $tester = new CommandCompletionTester($application->get('config:dump-reference')); $suggestions = $tester->complete($input); $this->assertSame($expectedSuggestions, $suggestions); diff --git a/Tests/Functional/ContainerDebugCommandTest.php b/Tests/Functional/ContainerDebugCommandTest.php index d21d4d113..e1e7ce084 100644 --- a/Tests/Functional/ContainerDebugCommandTest.php +++ b/Tests/Functional/ContainerDebugCommandTest.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\BackslashClass; use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ContainerExcluded; @@ -18,9 +20,7 @@ use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\HttpKernel\HttpKernelInterface; -/** - * @group functional - */ +#[Group('functional')] class ContainerDebugCommandTest extends AbstractWebTestCase { public function testDumpContainerIfNotExists() @@ -113,9 +113,7 @@ public function testExcludedService() $this->assertStringNotContainsString(ContainerExcluded::class, $tester->getDisplay()); } - /** - * @dataProvider provideIgnoreBackslashWhenFindingService - */ + #[DataProvider('provideIgnoreBackslashWhenFindingService')] public function testIgnoreBackslashWhenFindingService(string $validServiceId) { static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml']); @@ -168,25 +166,26 @@ public function testDescribeEnvVars() $this->assertStringMatchesFormat(<<<'TXT' -Symfony Container Environment Variables -======================================= + Symfony Container Environment Variables + ======================================= - --------- ----------------- ------------%w - Name Default value Real value%w - --------- ----------------- ------------%w - JSON "[1, "2.5", 3]" n/a%w - REAL n/a "value"%w - UNKNOWN n/a n/a%w - --------- ----------------- ------------%w + --------- ----------------- ------------%w + Name Default value Real value%w + --------- ----------------- ------------%w + JSON "[1, "2.5", 3]" n/a%w + REAL n/a "value"%w + UNKNOWN n/a n/a%w + --------- ----------------- ------------%w - // Note real values might be different between web and CLI.%w + // Note real values might be different between web and CLI.%w - [WARNING] The following variables are missing:%w + [WARNING] The following variables are missing:%w - * UNKNOWN + * UNKNOWN -TXT - , $tester->getDisplay(true)); + TXT, + $tester->getDisplay(true) + ); putenv('REAL'); } @@ -214,10 +213,10 @@ public function testGetDeprecation() file_put_contents($path, serialize([[ 'type' => 16384, 'message' => 'The "Symfony\Bundle\FrameworkBundle\Controller\Controller" class is deprecated since Symfony 4.2, use Symfony\Bundle\FrameworkBundle\Controller\AbstractController instead.', - 'file' => '/home/hamza/projet/contrib/sf/vendor/symfony/framework-bundle/Controller/Controller.php', + 'file' => '/home/hamza/project/contrib/sf/vendor/symfony/framework-bundle/Controller/Controller.php', 'line' => 17, 'trace' => [[ - 'file' => '/home/hamza/projet/contrib/sf/src/Controller/DefaultController.php', + 'file' => '/home/hamza/project/contrib/sf/src/Controller/DefaultController.php', 'line' => 9, 'function' => 'spl_autoload_call', ]], @@ -233,7 +232,7 @@ public function testGetDeprecation() $tester->assertCommandIsSuccessful(); $this->assertStringContainsString('Symfony\Bundle\FrameworkBundle\Controller\Controller', $tester->getDisplay()); - $this->assertStringContainsString('/home/hamza/projet/contrib/sf/vendor/symfony/framework-bundle/Controller/Controller.php', $tester->getDisplay()); + $this->assertStringContainsString('/home/hamza/project/contrib/sf/vendor/symfony/framework-bundle/Controller/Controller.php', $tester->getDisplay()); } public function testGetDeprecationNone() @@ -282,9 +281,7 @@ public static function provideIgnoreBackslashWhenFindingService(): array ]; } - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(array $input, array $expectedSuggestions, array $notExpectedSuggestions = []) { static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml', 'debug' => true]); diff --git a/Tests/Functional/ContainerLintCommandTest.php b/Tests/Functional/ContainerLintCommandTest.php index f0b6b4bd5..8e50caa01 100644 --- a/Tests/Functional/ContainerLintCommandTest.php +++ b/Tests/Functional/ContainerLintCommandTest.php @@ -11,19 +11,18 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\DependencyInjection\Argument\ArgumentTrait; -/** - * @group functional - */ +#[Group('functional')] class ContainerLintCommandTest extends AbstractWebTestCase { private Application $application; - /** - * @dataProvider containerLintProvider - */ + #[DataProvider('containerLintProvider')] public function testLintContainer(string $configFile, bool $resolveEnvVars, int $expectedExitCode, string $expectedOutput) { $kernel = static::createKernel([ @@ -40,13 +39,16 @@ public function testLintContainer(string $configFile, bool $resolveEnvVars, int $this->assertStringContainsString($expectedOutput, $tester->getDisplay()); } - public static function containerLintProvider(): array + public static function containerLintProvider(): iterable { - return [ - ['escaped_percent.yml', false, 0, 'The container was linted successfully'], - ['missing_env_var.yml', false, 0, 'The container was linted successfully'], - ['missing_env_var.yml', true, 1, 'Environment variable not found: "BAR"'], - ]; + yield ['escaped_percent.yml', false, 0, 'The container was linted successfully']; + + if (trait_exists(ArgumentTrait::class)) { + yield ['escaped_percent.yml', true, 0, 'The container was linted successfully']; + } + + yield ['missing_env_var.yml', false, 0, 'The container was linted successfully']; + yield ['missing_env_var.yml', true, 1, 'Environment variable not found: "BAR"']; } private function createCommandTester(): CommandTester diff --git a/Tests/Functional/DebugAutowiringCommandTest.php b/Tests/Functional/DebugAutowiringCommandTest.php index ca11e3fae..de94a1e71 100644 --- a/Tests/Functional/DebugAutowiringCommandTest.php +++ b/Tests/Functional/DebugAutowiringCommandTest.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; @@ -20,9 +22,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Routing\RouterInterface; -/** - * @group functional - */ +#[Group('functional')] class DebugAutowiringCommandTest extends AbstractWebTestCase { public function testBasicFunctionality() @@ -116,13 +116,11 @@ public function testNotConfusedByClassAliases() $this->assertStringContainsString(ClassAliasExampleClass::class, $tester->getDisplay()); } - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(array $input, array $expectedSuggestions) { $kernel = static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml']); - $command = (new Application($kernel))->add(new DebugAutowiringCommand()); + $command = (new Application($kernel))->addCommand(new DebugAutowiringCommand()); $tester = new CommandCompletionTester($command); diff --git a/Tests/Functional/FragmentTest.php b/Tests/Functional/FragmentTest.php index 48d5c327a..b8cff1f48 100644 --- a/Tests/Functional/FragmentTest.php +++ b/Tests/Functional/FragmentTest.php @@ -11,11 +11,11 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; + class FragmentTest extends AbstractWebTestCase { - /** - * @dataProvider getConfigs - */ + #[DataProvider('getConfigs')] public function testFragment($insulate) { $client = $this->createClient(['test_case' => 'Fragment', 'root_config' => 'config.yml', 'debug' => true]); @@ -26,15 +26,16 @@ public function testFragment($insulate) $client->request('GET', '/fragment_home'); $this->assertEquals(<<getResponse()->getContent()); + bar txt + -- + html + -- + es + -- + fr + TXT, + $client->getResponse()->getContent() + ); } public static function getConfigs() diff --git a/Tests/Functional/MailerTest.php b/Tests/Functional/MailerTest.php index 1ba71d74f..4193e3ff7 100644 --- a/Tests/Functional/MailerTest.php +++ b/Tests/Functional/MailerTest.php @@ -99,6 +99,7 @@ public function testMailerAssertions() $this->assertEmailHtmlBodyContains($email, 'Foo'); $this->assertEmailHtmlBodyNotContains($email, 'Bar'); $this->assertEmailAttachmentCount($email, 1); + $this->assertEmailAddressNotContains($email, 'To', 'thomas@symfony.com'); $email = $this->getMailerMessage($second); $this->assertEmailSubjectContains($email, 'Foo'); @@ -106,5 +107,7 @@ public function testMailerAssertions() $this->assertEmailAddressContains($email, 'To', 'fabien@symfony.com'); $this->assertEmailAddressContains($email, 'To', 'thomas@symfony.com'); $this->assertEmailAddressContains($email, 'Reply-To', 'me@symfony.com'); + $this->assertEmailAddressNotContains($email, 'To', 'helene@symfony.com'); + $this->assertEmailAddressNotContains($email, 'Reply-To', 'helene@symfony.com'); } } diff --git a/Tests/Functional/NotificationTest.php b/Tests/Functional/NotificationTest.php index 03b947a0f..7511591cb 100644 --- a/Tests/Functional/NotificationTest.php +++ b/Tests/Functional/NotificationTest.php @@ -11,11 +11,12 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\RequiresMethod; +use Symfony\Bundle\MercureBundle\MercureBundle; + final class NotificationTest extends AbstractWebTestCase { - /** - * @requires function \Symfony\Bundle\MercureBundle\MercureBundle::build - */ + #[RequiresMethod(MercureBundle::class, 'build')] public function testNotifierAssertion() { $client = $this->createClient(['test_case' => 'Notifier', 'root_config' => 'config.yml', 'debug' => true]); diff --git a/Tests/Functional/ProfilerTest.php b/Tests/Functional/ProfilerTest.php index d78259795..b5853dd1a 100644 --- a/Tests/Functional/ProfilerTest.php +++ b/Tests/Functional/ProfilerTest.php @@ -11,11 +11,11 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; + class ProfilerTest extends AbstractWebTestCase { - /** - * @dataProvider getConfigs - */ + #[DataProvider('getConfigs')] public function testProfilerIsDisabled($insulate) { $client = $this->createClient(['test_case' => 'Profiler', 'root_config' => 'config.yml']); @@ -36,9 +36,7 @@ public function testProfilerIsDisabled($insulate) $this->assertNull($client->getProfile()); } - /** - * @dataProvider getConfigs - */ + #[DataProvider('getConfigs')] public function testProfilerCollectParameter($insulate) { $client = $this->createClient(['test_case' => 'ProfilerCollectParameter', 'root_config' => 'config.yml']); diff --git a/Tests/Functional/PropertyInfoTest.php b/Tests/Functional/PropertyInfoTest.php index 18cd61b08..128932311 100644 --- a/Tests/Functional/PropertyInfoTest.php +++ b/Tests/Functional/PropertyInfoTest.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\TypeInfo\Type; @@ -29,9 +31,8 @@ public function testPhpDocPriority() $this->assertEquals(Type::list(Type::int()), $propertyInfo->getType(Dummy::class, 'codes')); } - /** - * @group legacy - */ + #[IgnoreDeprecations] + #[Group('legacy')] public function testPhpDocPriorityLegacy() { static::bootKernel(['test_case' => 'Serializer']); diff --git a/Tests/Functional/RouterDebugCommandTest.php b/Tests/Functional/RouterDebugCommandTest.php index 614078804..910e3b6f7 100644 --- a/Tests/Functional/RouterDebugCommandTest.php +++ b/Tests/Functional/RouterDebugCommandTest.php @@ -11,13 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\TestWith; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; -/** - * @group functional - */ +#[Group('functional')] class RouterDebugCommandTest extends AbstractWebTestCase { private Application $application; @@ -89,21 +90,17 @@ public function testSearchWithThrow() $tester->execute(['name' => 'gerard'], ['interactive' => true]); } - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testComplete(array $input, array $expectedSuggestions) { $tester = new CommandCompletionTester($this->application->get('debug:router')); $this->assertSame($expectedSuggestions, $tester->complete($input)); } - /** - * @testWith ["txt"] - * ["xml"] - * ["json"] - * ["md"] - */ + #[TestWith(['txt'])] + #[TestWith(['xml'])] + #[TestWith(['json'])] + #[TestWith(['md'])] public function testShowAliases(string $format) { $tester = $this->createCommandTester(); diff --git a/Tests/Functional/RoutingConditionServiceTest.php b/Tests/Functional/RoutingConditionServiceTest.php index 4f4caa6eb..f1f4f14cf 100644 --- a/Tests/Functional/RoutingConditionServiceTest.php +++ b/Tests/Functional/RoutingConditionServiceTest.php @@ -11,11 +11,11 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; + class RoutingConditionServiceTest extends AbstractWebTestCase { - /** - * @dataProvider provideRoutes - */ + #[DataProvider('provideRoutes')] public function testCondition(int $code, string $path) { $client = static::createClient(['test_case' => 'RoutingConditionService']); diff --git a/Tests/Functional/SecurityTest.php b/Tests/Functional/SecurityTest.php index c26fa717d..ab06b5f6c 100644 --- a/Tests/Functional/SecurityTest.php +++ b/Tests/Functional/SecurityTest.php @@ -11,13 +11,12 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\Security\Core\User\InMemoryUser; class SecurityTest extends AbstractWebTestCase { - /** - * @dataProvider getUsers - */ + #[DataProvider('getUsers')] public function testLoginUser(string $username, array $roles, ?string $firewallContext) { $user = new InMemoryUser($username, 'the-password', $roles); diff --git a/Tests/Functional/SessionTest.php b/Tests/Functional/SessionTest.php index 4c1b92ccf..88ea3230a 100644 --- a/Tests/Functional/SessionTest.php +++ b/Tests/Functional/SessionTest.php @@ -11,13 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\DataProvider; + class SessionTest extends AbstractWebTestCase { /** * Tests session attributes persist. - * - * @dataProvider getConfigs */ + #[DataProvider('getConfigs')] public function testWelcome($config, $insulate) { $client = $this->createClient(['test_case' => 'Session', 'root_config' => $config]); @@ -48,9 +49,8 @@ public function testWelcome($config, $insulate) /** * Tests flash messages work in practice. - * - * @dataProvider getConfigs */ + #[DataProvider('getConfigs')] public function testFlash($config, $insulate) { $client = $this->createClient(['test_case' => 'Session', 'root_config' => $config]); @@ -72,9 +72,8 @@ public function testFlash($config, $insulate) /** * See if two separate insulated clients can run without * polluting each other's session data. - * - * @dataProvider getConfigs */ + #[DataProvider('getConfigs')] public function testTwoClients($config, $insulate) { // start first client @@ -128,9 +127,7 @@ public function testTwoClients($config, $insulate) $this->assertStringContainsString('Welcome back client2, nice to meet you.', $crawler2->text()); } - /** - * @dataProvider getConfigs - */ + #[DataProvider('getConfigs')] public function testCorrectCacheControlHeadersForCacheableAction($config, $insulate) { $client = $this->createClient(['test_case' => 'Session', 'root_config' => $config]); diff --git a/Tests/Functional/SluggerLocaleAwareTest.php b/Tests/Functional/SluggerLocaleAwareTest.php index 769012461..d09f969b0 100644 --- a/Tests/Functional/SluggerLocaleAwareTest.php +++ b/Tests/Functional/SluggerLocaleAwareTest.php @@ -11,16 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Slugger\SlugConstructArgService; -/** - * @group functional - */ +#[Group('functional')] class SluggerLocaleAwareTest extends AbstractWebTestCase { - /** - * @requires extension intl - */ + #[RequiresPhpExtension('intl')] public function testLocalizedSlugger() { $kernel = static::createKernel(['test_case' => 'Slugger', 'root_config' => 'config.yml']); diff --git a/Tests/Functional/TestServiceContainerTest.php b/Tests/Functional/TestServiceContainerTest.php index fe7093081..8b8898ad8 100644 --- a/Tests/Functional/TestServiceContainerTest.php +++ b/Tests/Functional/TestServiceContainerTest.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use Symfony\Bundle\FrameworkBundle\Test\TestContainer; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\NonPublicService; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PrivateService; @@ -68,17 +70,13 @@ public function testSetDecoratedService() $this->assertSame($service, $container->get('decorated')->inner); } - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testBootKernel() { static::bootKernel(['test_case' => 'TestServiceContainer']); } - /** - * @depends testBootKernel - */ + #[Depends('testBootKernel')] public function testKernelIsNotInitialized() { self::assertNull(self::$class); diff --git a/Tests/Functional/TranslationDebugCommandTest.php b/Tests/Functional/TranslationDebugCommandTest.php index 5e396440c..1d7e2952b 100644 --- a/Tests/Functional/TranslationDebugCommandTest.php +++ b/Tests/Functional/TranslationDebugCommandTest.php @@ -11,13 +11,12 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; +use PHPUnit\Framework\Attributes\Group; use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandTester; -/** - * @group functional - */ +#[Group('functional')] class TranslationDebugCommandTest extends AbstractWebTestCase { private Application $application; diff --git a/Tests/Functional/app/AppKernel.php b/Tests/Functional/app/AppKernel.php index 59c28b2a6..5748b61cd 100644 --- a/Tests/Functional/app/AppKernel.php +++ b/Tests/Functional/app/AppKernel.php @@ -89,14 +89,16 @@ protected function build(ContainerBuilder $container): void $container->registerExtension(new TestDumpExtension()); } - public function __sleep(): array + public function __serialize(): array { - return ['varDir', 'testCase', 'rootConfig', 'environment', 'debug']; + return [$this->varDir, $this->testCase, $this->rootConfig, $this->environment, $this->debug]; } - public function __wakeup(): void + public function __unserialize(array $data): void { - foreach ($this as $k => $v) { + [$this->varDir, $this->testCase, $this->rootConfig, $this->environment, $this->debug] = $data; + + foreach ($this as $v) { if (\is_object($v)) { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } diff --git a/Tests/Kernel/ConcreteMicroKernel.php b/Tests/Kernel/ConcreteMicroKernel.php index a69618099..eac061e3b 100644 --- a/Tests/Kernel/ConcreteMicroKernel.php +++ b/Tests/Kernel/ConcreteMicroKernel.php @@ -55,12 +55,12 @@ public function getLogDir(): string return $this->cacheDir; } - public function __sleep(): array + public function __serialize(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } - public function __wakeup(): void + public function __unserialize(array $data): void { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } diff --git a/Tests/Kernel/flex-style/src/FlexStyleMicroKernel.php b/Tests/Kernel/flex-style/src/FlexStyleMicroKernel.php index 6f7c84d8b..5fa641c7f 100644 --- a/Tests/Kernel/flex-style/src/FlexStyleMicroKernel.php +++ b/Tests/Kernel/flex-style/src/FlexStyleMicroKernel.php @@ -64,12 +64,12 @@ public function getProjectDir(): string return \dirname((new \ReflectionObject($this))->getFileName(), 2); } - public function __sleep(): array + public function __serialize(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } - public function __wakeup(): void + public function __unserialize(array $data): void { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } diff --git a/Tests/Routing/RouterTest.php b/Tests/Routing/RouterTest.php index d2c021563..f46522a97 100644 --- a/Tests/Routing/RouterTest.php +++ b/Tests/Routing/RouterTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Routing; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\Routing\Router; @@ -438,9 +439,7 @@ public function testExceptionOnNonStringParameterWithSfContainer() $router->getRouteCollection(); } - /** - * @dataProvider getNonStringValues - */ + #[DataProvider('getNonStringValues')] public function testDefaultValuesAsNonStrings($value) { $routes = new RouteCollection(); @@ -455,9 +454,7 @@ public function testDefaultValuesAsNonStrings($value) $this->assertSame($value, $route->getDefault('foo')); } - /** - * @dataProvider getNonStringValues - */ + #[DataProvider('getNonStringValues')] public function testDefaultValuesAsNonStringsWithSfContainer($value) { $routes = new RouteCollection(); @@ -525,9 +522,7 @@ public static function getNonStringValues() return [[null], [false], [true], [new \stdClass()], [['foo', 'bar']], [[[]]]]; } - /** - * @dataProvider getContainerParameterForRoute - */ + #[DataProvider('getContainerParameterForRoute')] public function testCacheValidityWithContainerParameters($parameter) { $cacheDir = tempnam(sys_get_temp_dir(), 'sf_router_'); diff --git a/Tests/Secrets/SodiumVaultTest.php b/Tests/Secrets/SodiumVaultTest.php index f91f4bced..6d050386b 100644 --- a/Tests/Secrets/SodiumVaultTest.php +++ b/Tests/Secrets/SodiumVaultTest.php @@ -11,14 +11,13 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Secrets; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\String\LazyString; -/** - * @requires extension sodium - */ +#[RequiresPhpExtension('sodium')] class SodiumVaultTest extends TestCase { private string $secretsDir; diff --git a/Tests/Test/WebTestCaseTest.php b/Tests/Test/WebTestCaseTest.php index 84f2ef0ef..a058d3628 100644 --- a/Tests/Test/WebTestCaseTest.php +++ b/Tests/Test/WebTestCaseTest.php @@ -12,13 +12,16 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Test; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Attributes\RequiresMethod; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestAssertionsTrait; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\History; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\HttpFoundation\Cookie as HttpFoundationCookie; use Symfony\Component\HttpFoundation\Request; @@ -190,6 +193,42 @@ public function testAssertBrowserCookieValueSame() $this->getClientTester()->assertBrowserCookieValueSame('foo', 'babar', false, '/path'); } + #[RequiresMethod(History::class, 'isFirstPage')] + public function testAssertBrowserHistoryIsOnFirstPage() + { + $this->createHistoryTester('isFirstPage', true)->assertBrowserHistoryIsOnFirstPage(); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Failed asserting that the Browser history is on the first page.'); + $this->createHistoryTester('isFirstPage', false)->assertBrowserHistoryIsOnFirstPage(); + } + + #[RequiresMethod(History::class, 'isFirstPage')] + public function testAssertBrowserHistoryIsNotOnFirstPage() + { + $this->createHistoryTester('isFirstPage', false)->assertBrowserHistoryIsNotOnFirstPage(); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Failed asserting that the Browser history is not on the first page.'); + $this->createHistoryTester('isFirstPage', true)->assertBrowserHistoryIsNotOnFirstPage(); + } + + #[RequiresMethod(History::class, 'isLastPage')] + public function testAssertBrowserHistoryIsOnLastPage() + { + $this->createHistoryTester('isLastPage', true)->assertBrowserHistoryIsOnLastPage(); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Failed asserting that the Browser history is on the last page.'); + $this->createHistoryTester('isLastPage', false)->assertBrowserHistoryIsOnLastPage(); + } + + #[RequiresMethod(History::class, 'isLastPage')] + public function testAssertBrowserHistoryIsNotOnLastPage() + { + $this->createHistoryTester('isLastPage', false)->assertBrowserHistoryIsNotOnLastPage(); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Failed asserting that the Browser history is not on the last page.'); + $this->createHistoryTester('isLastPage', true)->assertBrowserHistoryIsNotOnLastPage(); + } + public function testAssertSelectorExists() { $this->getCrawlerTester(new Crawler('

'))->assertSelectorExists('body > h1'); @@ -386,6 +425,19 @@ private function getRequestTester(): WebTestCase return $this->getTester($client); } + private function createHistoryTester(string $method, bool $returnValue): WebTestCase + { + /** @var KernelBrowser&MockObject $client */ + $client = $this->createMock(KernelBrowser::class); + /** @var History&MockObject $history */ + $history = $this->createMock(History::class); + + $history->method($method)->willReturn($returnValue); + $client->method('getHistory')->willReturn($history); + + return $this->getTester($client); + } + private function getTester(KernelBrowser $client): WebTestCase { $tester = new class(method_exists($this, 'name') ? $this->name() : $this->getName()) extends WebTestCase { diff --git a/Tests/Translation/TranslatorTest.php b/Tests/Translation/TranslatorTest.php index e481a965e..d5f5d88eb 100644 --- a/Tests/Translation/TranslatorTest.php +++ b/Tests/Translation/TranslatorTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Translation; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Translation\Translator; use Symfony\Component\Config\Resource\DirectoryResource; @@ -130,7 +131,7 @@ public function testInvalidOptions() new Translator(new Container(), new MessageFormatter(), 'en', [], ['foo' => 'bar']); } - /** @dataProvider getDebugModeAndCacheDirCombinations */ + #[DataProvider('getDebugModeAndCacheDirCombinations')] public function testResourceFilesOptionLoadsBeforeOtherAddedResources($debug, $enableCache) { $someCatalogue = $this->getCatalogue('some_locale', []); diff --git a/composer.json b/composer.json index a00bac1c3..3f7a22462 100644 --- a/composer.json +++ b/composer.json @@ -19,61 +19,63 @@ "php": ">=8.2", "composer-runtime-api": ">=2.1", "ext-xml": "*", - "symfony/cache": "^6.4|^7.0", - "symfony/config": "^7.3", - "symfony/dependency-injection": "^7.2", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/config": "^7.3|^8.0", + "symfony/dependency-injection": "^7.2|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^7.3", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^7.3", - "symfony/http-kernel": "^7.2", + "symfony/error-handler": "^7.3|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^7.3|^8.0", + "symfony/http-kernel": "^7.2|^8.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/filesystem": "^7.1", - "symfony/finder": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0" + "symfony/polyfill-php85": "^1.32", + "symfony/filesystem": "^7.1|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0" }, "require-dev": { "doctrine/persistence": "^1.3|^2|^3", "dragonmantank/cron-expression": "^3.1", "seld/jsonlint": "^1.10", - "symfony/asset": "^6.4|^7.0", - "symfony/asset-mapper": "^6.4|^7.0", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/clock": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", - "symfony/dom-crawler": "^6.4|^7.0", - "symfony/dotenv": "^6.4|^7.0", + "symfony/asset": "^6.4|^7.0|^8.0", + "symfony/asset-mapper": "^6.4|^7.0|^8.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/dotenv": "^6.4|^7.0|^8.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/form": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/html-sanitizer": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/mailer": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/notifier": "^6.4|^7.0", - "symfony/object-mapper": "^v7.3.0-beta2", - "symfony/process": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/scheduler": "^6.4.4|^7.0.4", - "symfony/security-bundle": "^6.4|^7.0", - "symfony/semaphore": "^6.4|^7.0", - "symfony/serializer": "^7.2.5", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/string": "^6.4|^7.0", - "symfony/translation": "^7.3", - "symfony/twig-bundle": "^6.4|^7.0", - "symfony/type-info": "^7.1.8", - "symfony/validator": "^6.4|^7.0", - "symfony/workflow": "^7.3", - "symfony/yaml": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/json-streamer": "7.3.*", - "symfony/uid": "^6.4|^7.0", - "symfony/web-link": "^6.4|^7.0", - "symfony/webhook": "^7.2", + "symfony/form": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/html-sanitizer": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/mailer": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/notifier": "^6.4|^7.0|^8.0", + "symfony/object-mapper": "^7.3|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0", + "symfony/scheduler": "^6.4.4|^7.0.4|^8.0", + "symfony/security-bundle": "^6.4|^7.0|^8.0", + "symfony/semaphore": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.2.5|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/translation": "^7.3|^8.0", + "symfony/twig-bundle": "^6.4|^7.0|^8.0", + "symfony/type-info": "^7.1.8|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/workflow": "^7.3|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/json-streamer": "^7.3|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/web-link": "^6.4|^7.0|^8.0", + "symfony/webhook": "^7.2|^8.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "twig/twig": "^3.12" }, @@ -89,12 +91,10 @@ "symfony/dom-crawler": "<6.4", "symfony/http-client": "<6.4", "symfony/form": "<6.4", - "symfony/json-streamer": ">=7.4", "symfony/lock": "<6.4", "symfony/mailer": "<6.4", "symfony/messenger": "<6.4", "symfony/mime": "<6.4", - "symfony/object-mapper": ">=7.4", "symfony/property-info": "<6.4", "symfony/property-access": "<6.4", "symfony/runtime": "<6.4.13|>=7.0,<7.1.6", @@ -117,5 +117,10 @@ "/Tests/" ] }, - "minimum-stability": "dev" + "minimum-stability": "dev", + "config": { + "allow-plugins": { + "symfony/runtime": false + } + } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d00ee0f1e..90e1a751e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,11 @@ @@ -20,7 +21,7 @@ - + ./ @@ -29,5 +30,9 @@ ./Tests ./vendor - + + + + +