diff --git a/.appveyor.yml b/.appveyor.yml index 67dcba9b569f6..383bbfdbb6493 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -18,8 +18,8 @@ install: - cd ext - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_apcu-5.1.21-8.1-ts-vs16-x86.zip - 7z x php_apcu-5.1.21-8.1-ts-vs16-x86.zip -y >nul - - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_redis-5.3.7rc1-8.1-ts-vs16-x86.zip - - 7z x php_redis-5.3.7rc1-8.1-ts-vs16-x86.zip -y >nul + - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_redis-5.3.7-8.1-ts-vs16-x86.zip + - 7z x php_redis-5.3.7-8.1-ts-vs16-x86.zip -y >nul - cd .. - copy /Y php.ini-development php.ini-min - echo memory_limit=-1 >> php.ini-min diff --git a/.github/expected-missing-return-types.diff b/.github/expected-missing-return-types.diff index cf4c237e3070c..7b0cdb0801635 100644 --- a/.github/expected-missing-return-types.diff +++ b/.github/expected-missing-return-types.diff @@ -7,10 +7,10 @@ head=$(sed '/^diff /Q' .github/expected-missing-return-types.diff) git checkout composer.json src/ diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php -index 165797504b..0c0922088a 100644 +index 32bac9c42f..df89815902 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php -@@ -87,5 +87,5 @@ abstract class KernelTestCase extends TestCase +@@ -88,5 +88,5 @@ abstract class KernelTestCase extends TestCase * @return Container */ - protected static function getContainer(): ContainerInterface @@ -18,7 +18,7 @@ index 165797504b..0c0922088a 100644 { if (!static::$booted) { diff --git a/src/Symfony/Component/BrowserKit/AbstractBrowser.php b/src/Symfony/Component/BrowserKit/AbstractBrowser.php -index ac25bdf4be..949a036abd 100644 +index b27ca37529..5b80175850 100644 --- a/src/Symfony/Component/BrowserKit/AbstractBrowser.php +++ b/src/Symfony/Component/BrowserKit/AbstractBrowser.php @@ -408,5 +408,5 @@ abstract class AbstractBrowser @@ -50,10 +50,10 @@ index ac25bdf4be..949a036abd 100644 { return $response; diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php -index 89d6adb70e..c569992efc 100644 +index 7cda0bc7d8..b2311826f4 100644 --- a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php -@@ -108,5 +108,5 @@ class NodeBuilder implements NodeParentInterface +@@ -111,5 +111,5 @@ class NodeBuilder implements NodeParentInterface * @return NodeDefinition&ParentNodeDefinitionInterface */ - public function end() @@ -71,12 +71,12 @@ index 7b5d443fe6..d64ae0d024 100644 + public function getConfigTreeBuilder(): TreeBuilder; } diff --git a/src/Symfony/Component/Config/FileLocator.php b/src/Symfony/Component/Config/FileLocator.php -index 21122e52c9..206029e705 100644 +index ab18232db1..bc5af7f89c 100644 --- a/src/Symfony/Component/Config/FileLocator.php +++ b/src/Symfony/Component/Config/FileLocator.php -@@ -34,5 +34,5 @@ class FileLocator implements FileLocatorInterface - * {@inheritdoc} - */ +@@ -31,5 +31,5 @@ class FileLocator implements FileLocatorInterface + } + - public function locate(string $name, string $currentPath = null, bool $first = true) + public function locate(string $name, string $currentPath = null, bool $first = true): string|array { @@ -103,10 +103,10 @@ index c479f75d34..0d16baaff7 100644 { if (\is_string($resource) && \strlen($resource) !== ($i = strcspn($resource, '*?{[')) && !str_contains($resource, "\n")) { diff --git a/src/Symfony/Component/Config/Loader/Loader.php b/src/Symfony/Component/Config/Loader/Loader.php -index e0974fb151..f698b5271b 100644 +index 2ab50b021b..b64e1e1055 100644 --- a/src/Symfony/Component/Config/Loader/Loader.php +++ b/src/Symfony/Component/Config/Loader/Loader.php -@@ -50,5 +50,5 @@ abstract class Loader implements LoaderInterface +@@ -44,5 +44,5 @@ abstract class Loader implements LoaderInterface * @return mixed */ - public function import(mixed $resource, string $type = null) @@ -156,52 +156,52 @@ index 6b1c6c5fbe..bb80ed461e 100644 + public function isFresh(ResourceInterface $resource, int $timestamp): bool; } diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php -index 64068fcc23..f29aaf1b94 100644 +index c8991a81ee..3be8925a68 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php -@@ -218,5 +218,5 @@ class Application implements ResetInterface +@@ -220,5 +220,5 @@ class Application implements ResetInterface * @return int 0 if everything went fine, or an error code */ - public function doRun(InputInterface $input, OutputInterface $output) + public function doRun(InputInterface $input, OutputInterface $output): int { if (true === $input->hasParameterOption(['--version', '-V'], true)) { -@@ -454,5 +454,5 @@ class Application implements ResetInterface +@@ -465,5 +465,5 @@ class Application implements ResetInterface * @return string */ - public function getLongVersion() + public function getLongVersion(): string { if ('UNKNOWN' !== $this->getName()) { -@@ -497,5 +497,5 @@ class Application implements ResetInterface +@@ -508,5 +508,5 @@ class Application implements ResetInterface * @return Command|null */ - public function add(Command $command) + public function add(Command $command): ?Command { $this->init(); -@@ -534,5 +534,5 @@ class Application implements ResetInterface +@@ -545,5 +545,5 @@ class Application implements ResetInterface * @throws CommandNotFoundException When given command name does not exist */ - public function get(string $name) + public function get(string $name): Command { $this->init(); -@@ -641,5 +641,5 @@ class Application implements ResetInterface +@@ -652,5 +652,5 @@ class Application implements ResetInterface * @throws CommandNotFoundException When command name is incorrect or ambiguous */ - public function find(string $name) + public function find(string $name): Command { $this->init(); -@@ -751,5 +751,5 @@ class Application implements ResetInterface +@@ -762,5 +762,5 @@ class Application implements ResetInterface * @return Command[] */ - public function all(string $namespace = null) + public function all(string $namespace = null): array { $this->init(); -@@ -950,5 +950,5 @@ class Application implements ResetInterface +@@ -961,5 +961,5 @@ class Application implements ResetInterface * @return int 0 if everything went fine, or an error code */ - protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) @@ -209,30 +209,37 @@ index 64068fcc23..f29aaf1b94 100644 { foreach ($command->getHelperSet() as $helper) { diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php -index 0a3f4b7889..18c2312399 100644 +index 1e3c1a5a2b..923ab29a4d 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php -@@ -188,5 +188,5 @@ class Command +@@ -192,5 +192,5 @@ class Command * @return bool */ - public function isEnabled() + public function isEnabled(): bool { return true; -@@ -214,5 +214,5 @@ class Command +@@ -218,5 +218,5 @@ class Command * @see setCode() */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { throw new LogicException('You must override the execute() method in the concrete command class.'); +@@ -687,5 +687,5 @@ class Command + * @throws InvalidArgumentException if the helper is not defined + */ +- public function getHelper(string $name): mixed ++ public function getHelper(string $name): HelperInterface + { + if (null === $this->helperSet) { diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatter.php b/src/Symfony/Component/Console/Formatter/OutputFormatter.php -index 3c6b0efccd..121664f15a 100644 +index 38e75c3178..e5db4d626e 100644 --- a/src/Symfony/Component/Console/Formatter/OutputFormatter.php +++ b/src/Symfony/Component/Console/Formatter/OutputFormatter.php -@@ -137,5 +137,5 @@ class OutputFormatter implements WrappableOutputFormatterInterface - * {@inheritdoc} - */ +@@ -116,5 +116,5 @@ class OutputFormatter implements WrappableOutputFormatterInterface + } + - public function formatAndWrap(?string $message, int $width) + public function formatAndWrap(?string $message, int $width): string { @@ -248,7 +255,7 @@ index 746cd27e79..52c61429cf 100644 + public function formatAndWrap(?string $message, int $width): string; } diff --git a/src/Symfony/Component/Console/Helper/HelperInterface.php b/src/Symfony/Component/Console/Helper/HelperInterface.php -index 1d2b7bfb84..cb1f66152d 100644 +index 2762cdf05c..737334268a 100644 --- a/src/Symfony/Component/Console/Helper/HelperInterface.php +++ b/src/Symfony/Component/Console/Helper/HelperInterface.php @@ -34,4 +34,4 @@ interface HelperInterface @@ -257,6 +264,17 @@ index 1d2b7bfb84..cb1f66152d 100644 - public function getName(); + public function getName(): string; } +diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php +index e21aee2c00..4d1bc22bee 100644 +--- a/src/Symfony/Component/Console/Helper/Table.php ++++ b/src/Symfony/Component/Console/Helper/Table.php +@@ -193,5 +193,5 @@ class Table + * @return $this + */ +- public function setRows(array $rows) ++ public function setRows(array $rows): static + { + $this->rows = []; diff --git a/src/Symfony/Component/Console/Input/InputInterface.php b/src/Symfony/Component/Console/Input/InputInterface.php index 3af991a76f..742e2508f3 100644 --- a/src/Symfony/Component/Console/Input/InputInterface.php @@ -283,10 +301,10 @@ index 3af991a76f..742e2508f3 100644 /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php -index 70b6c91ff5..cfced387f3 100644 +index afc8183571..2cd43ea0af 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php -@@ -71,5 +71,5 @@ abstract class AbstractRecursivePass implements CompilerPassInterface +@@ -68,5 +68,5 @@ abstract class AbstractRecursivePass implements CompilerPassInterface * @return mixed */ - protected function processValue(mixed $value, bool $isRoot = false) @@ -294,22 +312,22 @@ index 70b6c91ff5..cfced387f3 100644 { if (\is_array($value)) { diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php -index 04b7022484..5d736ec754 100644 +index 9c830e77c8..e530cafb43 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php -@@ -108,5 +108,5 @@ class Container implements ContainerInterface, ResetInterface - * @throws InvalidArgumentException if the parameter is not defined +@@ -109,5 +109,5 @@ class Container implements ContainerInterface, ResetInterface + * @throws ParameterNotFoundException if the parameter is not defined */ - public function getParameter(string $name) + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null { return $this->parameterBag->get($name); diff --git a/src/Symfony/Component/DependencyInjection/ContainerInterface.php b/src/Symfony/Component/DependencyInjection/ContainerInterface.php -index cad44026c0..14cd192e07 100644 +index 9e97fb71fc..1cda97c611 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerInterface.php +++ b/src/Symfony/Component/DependencyInjection/ContainerInterface.php @@ -53,5 +53,5 @@ interface ContainerInterface extends PsrContainerInterface - * @throws InvalidArgumentException if the parameter is not defined + * @throws ParameterNotFoundException if the parameter is not defined */ - public function getParameter(string $name); + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null; @@ -326,56 +344,55 @@ index a42967f4da..4e86e16f9d 100644 + public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface; } diff --git a/src/Symfony/Component/DependencyInjection/Extension/Extension.php b/src/Symfony/Component/DependencyInjection/Extension/Extension.php -index d553203c43..1163f4b107 100644 +index 00192d0da5..620efa4fd1 100644 --- a/src/Symfony/Component/DependencyInjection/Extension/Extension.php +++ b/src/Symfony/Component/DependencyInjection/Extension/Extension.php -@@ -32,5 +32,5 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn - * {@inheritdoc} - */ +@@ -29,10 +29,10 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn + private array $processedConfigs = []; + - public function getXsdValidationBasePath() + public function getXsdValidationBasePath(): string|false { return false; -@@ -40,5 +40,5 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn - * {@inheritdoc} - */ + } + - public function getNamespace() + public function getNamespace(): string { return 'http://example.org/schema/dic/'.$this->getAlias(); -@@ -77,5 +77,5 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn - * {@inheritdoc} - */ +@@ -68,5 +68,5 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn + } + - public function getConfiguration(array $config, ContainerBuilder $container) + public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface { $class = static::class; diff --git a/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php b/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php -index f2373ed5ea..1eec21a938 100644 +index 11cda00cc5..07b4990160 100644 --- a/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php +++ b/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php -@@ -33,5 +33,5 @@ interface ExtensionInterface +@@ -35,5 +35,5 @@ interface ExtensionInterface * @return string */ - public function getNamespace(); + public function getNamespace(): string; /** -@@ -40,5 +40,5 @@ interface ExtensionInterface +@@ -42,5 +42,5 @@ interface ExtensionInterface * @return string|false */ - public function getXsdValidationBasePath(); + public function getXsdValidationBasePath(): string|false; /** -@@ -49,4 +49,4 @@ interface ExtensionInterface +@@ -51,4 +51,4 @@ interface ExtensionInterface * @return string */ - public function getAlias(); + public function getAlias(): string; } diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php -index a9d78115dd..8b3b420a9c 100644 +index f4c6b29258..1402331f9e 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php @@ -31,4 +31,4 @@ interface InstantiatorInterface @@ -405,17 +422,17 @@ index 479aeef880..272954c082 100644 + public function getFunctions(): array; } diff --git a/src/Symfony/Component/Form/AbstractExtension.php b/src/Symfony/Component/Form/AbstractExtension.php -index 79d61e8bc0..7f34d95d84 100644 +index 5d3c1a8480..7eb6bba474 100644 --- a/src/Symfony/Component/Form/AbstractExtension.php +++ b/src/Symfony/Component/Form/AbstractExtension.php -@@ -114,5 +114,5 @@ abstract class AbstractExtension implements FormExtensionInterface +@@ -99,5 +99,5 @@ abstract class AbstractExtension implements FormExtensionInterface * @return FormTypeInterface[] */ - protected function loadTypes() + protected function loadTypes(): array { return []; -@@ -134,5 +134,5 @@ abstract class AbstractExtension implements FormExtensionInterface +@@ -119,5 +119,5 @@ abstract class AbstractExtension implements FormExtensionInterface * @return FormTypeGuesserInterface|null */ - protected function loadTypeGuesser() @@ -423,10 +440,10 @@ index 79d61e8bc0..7f34d95d84 100644 { return null; diff --git a/src/Symfony/Component/Form/AbstractRendererEngine.php b/src/Symfony/Component/Form/AbstractRendererEngine.php -index ada182f57b..0d2591f7a7 100644 +index f79f1c1338..c74c4bf2f1 100644 --- a/src/Symfony/Component/Form/AbstractRendererEngine.php +++ b/src/Symfony/Component/Form/AbstractRendererEngine.php -@@ -137,5 +137,5 @@ abstract class AbstractRendererEngine implements FormRendererEngineInterface, Re +@@ -125,5 +125,5 @@ abstract class AbstractRendererEngine implements FormRendererEngineInterface, Re * @return bool */ - abstract protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName); @@ -434,35 +451,34 @@ index ada182f57b..0d2591f7a7 100644 /** diff --git a/src/Symfony/Component/Form/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php -index 3325b8bc27..1cc22a1dab 100644 +index da401930d7..15d6219259 100644 --- a/src/Symfony/Component/Form/AbstractType.php +++ b/src/Symfony/Component/Form/AbstractType.php -@@ -52,5 +52,5 @@ abstract class AbstractType implements FormTypeInterface - * {@inheritdoc} - */ +@@ -37,10 +37,10 @@ abstract class AbstractType implements FormTypeInterface + } + - public function getBlockPrefix() + public function getBlockPrefix(): string { return StringUtil::fqcnToBlockPrefix(static::class) ?: ''; -@@ -60,5 +60,5 @@ abstract class AbstractType implements FormTypeInterface - * {@inheritdoc} - */ + } + - public function getParent() + public function getParent(): ?string { return FormType::class; diff --git a/src/Symfony/Component/Form/DataTransformerInterface.php b/src/Symfony/Component/Form/DataTransformerInterface.php -index 8495905e17..1e53be60be 100644 +index edb3f83c0b..c54659f95e 100644 --- a/src/Symfony/Component/Form/DataTransformerInterface.php +++ b/src/Symfony/Component/Form/DataTransformerInterface.php -@@ -60,5 +60,5 @@ interface DataTransformerInterface +@@ -63,5 +63,5 @@ interface DataTransformerInterface * @throws TransformationFailedException when the transformation fails */ - public function transform(mixed $value); + public function transform(mixed $value): mixed; /** -@@ -89,4 +89,4 @@ interface DataTransformerInterface +@@ -92,4 +92,4 @@ interface DataTransformerInterface * @throws TransformationFailedException when the transformation fails */ - public function reverseTransform(mixed $value); @@ -568,10 +584,10 @@ index 1cb865fd66..f6f4efe7a7 100644 + public function getName(): string; } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php -index 45ff4a006c..611259b3b6 100644 +index f3302952a9..a5be1dc2f0 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php -@@ -448,5 +448,5 @@ class HttpCache implements HttpKernelInterface, TerminableInterface +@@ -457,5 +457,5 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * @return Response */ - protected function forward(Request $request, bool $catch = false, Response $entry = null) @@ -579,17 +595,17 @@ index 45ff4a006c..611259b3b6 100644 { $this->surrogate?->addSurrogateCapability($request); diff --git a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php -index 1557da575a..7e5a2a9859 100644 +index 0938617169..f4b9c76218 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php +++ b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php -@@ -61,5 +61,5 @@ class HttpKernelBrowser extends AbstractBrowser +@@ -59,5 +59,5 @@ class HttpKernelBrowser extends AbstractBrowser * @return Response */ - protected function doRequest(object $request) + protected function doRequest(object $request): Response { $response = $this->kernel->handle($request, HttpKernelInterface::MAIN_REQUEST, $this->catchExceptions); -@@ -79,5 +79,5 @@ class HttpKernelBrowser extends AbstractBrowser +@@ -75,5 +75,5 @@ class HttpKernelBrowser extends AbstractBrowser * @return string */ - protected function getScript(object $request) @@ -597,17 +613,17 @@ index 1557da575a..7e5a2a9859 100644 { $kernel = var_export(serialize($this->kernel), true); diff --git a/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php b/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php -index 19ff0db181..f0f4a5829f 100644 +index 84452ae71f..f70165d25f 100644 --- a/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php +++ b/src/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php -@@ -30,5 +30,5 @@ interface DebugLoggerInterface - * @return array +@@ -34,5 +34,5 @@ interface DebugLoggerInterface + * }> */ - public function getLogs(Request $request = null); + public function getLogs(Request $request = null): array; /** -@@ -37,5 +37,5 @@ interface DebugLoggerInterface +@@ -41,5 +41,5 @@ interface DebugLoggerInterface * @return int */ - public function countErrors(Request $request = null); @@ -633,10 +649,10 @@ index 125b6eae50..ac327e8981 100644 { $lock = new Lock($key, $this->store, $ttl, $autoRelease); diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransport.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransport.php -index 297fccbd3f..4c47d95b38 100644 +index c8ba7f9c73..f36408603d 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransport.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/AmazonSqsTransport.php -@@ -109,5 +109,5 @@ class AmazonSqsTransport implements TransportInterface, SetupableTransportInterf +@@ -91,5 +91,5 @@ class AmazonSqsTransport implements TransportInterface, SetupableTransportInterf * @return MessageCountAwareInterface&ReceiverInterface */ - private function getReceiver(): ReceiverInterface @@ -644,7 +660,7 @@ index 297fccbd3f..4c47d95b38 100644 { return $this->receiver ??= new AmazonSqsReceiver($this->connection, $this->serializer); diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php -index 205c15b4cd..e93e460ed1 100644 +index e22265f1dc..af4856ca05 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -486,5 +486,5 @@ class OptionsResolver implements Options @@ -683,61 +699,50 @@ index 205c15b4cd..e93e460ed1 100644 { if ($this->locked) { diff --git a/src/Symfony/Component/PropertyAccess/PropertyPathInterface.php b/src/Symfony/Component/PropertyAccess/PropertyPathInterface.php -index fbb37d9f94..522e0487a9 100644 +index 11f6ed405b..53e7b33451 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyPathInterface.php +++ b/src/Symfony/Component/PropertyAccess/PropertyPathInterface.php -@@ -31,5 +31,5 @@ interface PropertyPathInterface extends \Traversable +@@ -33,5 +33,5 @@ interface PropertyPathInterface extends \Traversable * @return int */ - public function getLength(); + public function getLength(): int; /** -@@ -43,5 +43,5 @@ interface PropertyPathInterface extends \Traversable +@@ -45,5 +45,5 @@ interface PropertyPathInterface extends \Traversable * @return self|null */ - public function getParent(); + public function getParent(): ?\Symfony\Component\PropertyAccess\PropertyPathInterface; /** -@@ -50,5 +50,5 @@ interface PropertyPathInterface extends \Traversable +@@ -52,5 +52,5 @@ interface PropertyPathInterface extends \Traversable * @return list */ - public function getElements(); + public function getElements(): array; /** -@@ -61,5 +61,5 @@ interface PropertyPathInterface extends \Traversable +@@ -63,5 +63,5 @@ interface PropertyPathInterface extends \Traversable * @throws Exception\OutOfBoundsException If the offset is invalid */ - public function getElement(int $index); + public function getElement(int $index): string; /** -@@ -72,5 +72,5 @@ interface PropertyPathInterface extends \Traversable +@@ -74,5 +74,5 @@ interface PropertyPathInterface extends \Traversable * @throws Exception\OutOfBoundsException If the offset is invalid */ - public function isProperty(int $index); + public function isProperty(int $index): bool; /** -@@ -83,4 +83,4 @@ interface PropertyPathInterface extends \Traversable +@@ -85,4 +85,4 @@ interface PropertyPathInterface extends \Traversable * @throws Exception\OutOfBoundsException If the offset is invalid */ - public function isIndex(int $index); + public function isIndex(int $index): bool; } -diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php -index 38e563e177..ee9c00a1db 100644 ---- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php -+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php -@@ -742,5 +742,5 @@ class PropertyAccessorTest extends TestCase - * @return mixed - */ -- public function getFoo() -+ public function getFoo(): mixed - { - return $this->foo; diff --git a/src/Symfony/Component/PropertyInfo/PropertyAccessExtractorInterface.php b/src/Symfony/Component/PropertyInfo/PropertyAccessExtractorInterface.php index f9ee787130..61f8b6d5be 100644 --- a/src/Symfony/Component/PropertyInfo/PropertyAccessExtractorInterface.php @@ -776,10 +781,10 @@ index 6da0bcb4c8..16e9765b1d 100644 + public function getTypes(string $class, string $property, array $context = []): ?array; } diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php -index 204e9b3341..8e624e1154 100644 +index b63dc5c85e..e53bb49369 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php -@@ -253,5 +253,5 @@ abstract class AnnotationClassLoader implements LoaderInterface +@@ -244,5 +244,5 @@ abstract class AnnotationClassLoader implements LoaderInterface * @return string */ - protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) @@ -787,12 +792,12 @@ index 204e9b3341..8e624e1154 100644 { $name = str_replace('\\', '_', $class->name).'_'.$method->name; diff --git a/src/Symfony/Component/Routing/Router.php b/src/Symfony/Component/Routing/Router.php -index 1142fc9714..9e4965ce06 100644 +index 88e4e3f2ef..3736bf8c00 100644 --- a/src/Symfony/Component/Routing/Router.php +++ b/src/Symfony/Component/Routing/Router.php -@@ -178,5 +178,5 @@ class Router implements RouterInterface, RequestMatcherInterface - * {@inheritdoc} - */ +@@ -175,5 +175,5 @@ class Router implements RouterInterface, RequestMatcherInterface + } + - public function getRouteCollection() + public function getRouteCollection(): RouteCollection { @@ -808,7 +813,7 @@ index 6912f8a15b..caf18c886a 100644 + public function getRouteCollection(): RouteCollection; } diff --git a/src/Symfony/Component/Security/Core/Authentication/RememberMe/TokenProviderInterface.php b/src/Symfony/Component/Security/Core/Authentication/RememberMe/TokenProviderInterface.php -index eda4730004..00cfc5b9c7 100644 +index 9b32fdce31..fbbd65d8b7 100644 --- a/src/Symfony/Component/Security/Core/Authentication/RememberMe/TokenProviderInterface.php +++ b/src/Symfony/Component/Security/Core/Authentication/RememberMe/TokenProviderInterface.php @@ -28,5 +28,5 @@ interface TokenProviderInterface @@ -819,11 +824,11 @@ index eda4730004..00cfc5b9c7 100644 /** diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.php b/src/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.php -index 7e401c3ff3..6b446ff376 100644 +index ba52c8ea65..e879a84982 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.php -@@ -36,4 +36,4 @@ interface VoterInterface - * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED +@@ -37,4 +37,4 @@ interface VoterInterface + * @psalm-return self::ACCESS_* must be transformed into @return on Symfony 7 */ - public function vote(TokenInterface $token, mixed $subject, array $attributes); + public function vote(TokenInterface $token, mixed $subject, array $attributes): int; @@ -868,12 +873,12 @@ index 91271d14a3..100c2fb549 100644 + public function start(Request $request, AuthenticationException $authException = null): Response; } diff --git a/src/Symfony/Component/Security/Http/Firewall.php b/src/Symfony/Component/Security/Http/Firewall.php -index 0c313f8f09..acfc9f4b88 100644 +index 69be986785..a32d9871d9 100644 --- a/src/Symfony/Component/Security/Http/Firewall.php +++ b/src/Symfony/Component/Security/Http/Firewall.php -@@ -106,5 +106,5 @@ class Firewall implements EventSubscriberInterface - * {@inheritdoc} - */ +@@ -103,5 +103,5 @@ class Firewall implements EventSubscriberInterface + } + - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { @@ -889,7 +894,7 @@ index 480ea8ad6b..fa43d6a6e9 100644 + public function getListeners(Request $request): array; } diff --git a/src/Symfony/Component/Serializer/Encoder/DecoderInterface.php b/src/Symfony/Component/Serializer/Encoder/DecoderInterface.php -index f38069e471..0966eb3e89 100644 +index 84a84ad1f3..6f66b6d32a 100644 --- a/src/Symfony/Component/Serializer/Encoder/DecoderInterface.php +++ b/src/Symfony/Component/Serializer/Encoder/DecoderInterface.php @@ -35,5 +35,5 @@ interface DecoderInterface @@ -906,24 +911,24 @@ index f38069e471..0966eb3e89 100644 + public function supportsDecoding(string $format): bool; } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php -index 44ba45f581..3398115497 100644 +index 12c778cb80..4ad55fb3e1 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php -@@ -213,5 +213,5 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn - * @return string[]|AttributeMetadataInterface[]|bool +@@ -210,5 +210,5 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn + * @throws LogicException if the 'allow_extra_attributes' context variable is false and no class metadata factory is provided */ - protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false) + protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool { $allowExtraAttributes = $context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES]; -@@ -263,5 +263,5 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn +@@ -260,5 +260,5 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn * @return bool */ - protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []) + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []): bool { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; -@@ -314,5 +314,5 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn +@@ -311,5 +311,5 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn * @throws MissingConstructorArgumentsException */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) @@ -931,60 +936,57 @@ index 44ba45f581..3398115497 100644 { if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php -index 511dd1c724..c319e1839b 100644 +index febe70bb47..bf4ca2e325 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php -@@ -139,5 +139,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer +@@ -137,10 +137,10 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */) + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return \is_object($data) && !$data instanceof \Traversable; -@@ -147,5 +147,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer - * {@inheritdoc} - */ + } + - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if (!isset($context['cache_key'])) { -@@ -265,5 +265,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer - * {@inheritdoc} - */ +@@ -222,5 +222,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer + } + - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object { if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { -@@ -327,5 +327,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer +@@ -284,5 +284,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer * @return string[] */ - abstract protected function extractAttributes(object $object, string $format = null, array $context = []); + abstract protected function extractAttributes(object $object, string $format = null, array $context = []): array; /** -@@ -334,5 +334,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer +@@ -291,15 +291,15 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer * @return mixed */ - abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []); + abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed; /** -@@ -341,5 +341,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */) + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool { return class_exists($type) || (interface_exists($type, false) && $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); -@@ -349,5 +349,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer - * {@inheritdoc} - */ + } + - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { if (!isset($context['cache_key'])) { diff --git a/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php b/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php -index 1c708738a1..3b6c9d5056 100644 +index ae3adbfe33..3a38429cf1 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php +++ b/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php @@ -45,5 +45,5 @@ interface DenormalizerInterface @@ -1001,7 +1003,7 @@ index 1c708738a1..3b6c9d5056 100644 + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool; } diff --git a/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php b/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php -index 741f19e50b..acf3be931b 100644 +index 691e9c70f0..fc87f672e1 100644 --- a/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php +++ b/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php @@ -37,5 +37,5 @@ interface NormalizerInterface @@ -1077,7 +1079,7 @@ index ee1d68c78f..9baaabb04c 100644 { return self::PROPERTY_CONSTRAINT; diff --git a/src/Symfony/Component/VarExporter/Internal/Exporter.php b/src/Symfony/Component/VarExporter/Internal/Exporter.php -index f7ef22df5c..9439e9526f 100644 +index 57c229eb14..b9aa92fcb7 100644 --- a/src/Symfony/Component/VarExporter/Internal/Exporter.php +++ b/src/Symfony/Component/VarExporter/Internal/Exporter.php @@ -36,5 +36,5 @@ class Exporter diff --git a/.github/patch-types.php b/.github/patch-types.php index a5ff5b3293c15..eaa085bfae9bb 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -12,8 +12,15 @@ Symfony\Component\ErrorHandler\DebugClassLoader::enable(); foreach ($loader->getClassMap() as $class => $file) { + $file = realpath($file); + switch (true) { - case false !== strpos($file = realpath($file), '/vendor/'): + case false !== strpos($file, '/src/Symfony/Component/Cache/Traits/Redis'): + if (!str_ends_with($file, 'Proxy.php')) { + break; + } + // no break; + case false !== strpos($file, '/vendor/'): case false !== strpos($file, '/src/Symfony/Bridge/PhpUnit/'): case false !== strpos($file, '/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Article.php'): case false !== strpos($file, '/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php'): @@ -46,6 +53,7 @@ case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionUnionTypeWithIntersectionFixture.php'): + case false !== strpos($file, '/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyProxy/ReadOnlyClass.php'): continue 2; } diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 2d2ec7a1c271e..cd7421521a403 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -134,7 +134,7 @@ jobs: uses: shivammathur/setup-php@v2 with: coverage: "none" - extensions: "json,couchbase-3.2.2,memcached,mongodb-1.12.0,redis-5.3.4,rdkafka,xsl,ldap" + extensions: "json,couchbase-3.2.2,memcached,mongodb-1.12.0,redis,rdkafka,xsl,ldap" ini-values: date.timezone=Europe/Paris,memory_limit=-1,default_socket_timeout=10,session.gc_probability=0,apc.enable_cli=1,zend.assertions=1 php-version: "${{ matrix.php }}" tools: pecl diff --git a/.github/workflows/package-tests.yml b/.github/workflows/package-tests.yml index 40c0e66c573ea..e551ccc5d2e1c 100644 --- a/.github/workflows/package-tests.yml +++ b/.github/workflows/package-tests.yml @@ -21,7 +21,7 @@ jobs: - name: Find packages id: find-packages - run: echo "::set-output name=packages::$(php .github/get-modified-packages.php $(find src/Symfony -mindepth 2 -maxdepth 6 -type f -name composer.json -printf '%h\n' | jq -R -s -c 'split("\n")[:-1]') $(git diff --name-only origin/${{ github.base_ref }} HEAD | grep src/ | jq -R -s -c 'split("\n")[:-1]'))" + run: echo "::set-output name=packages::$(php .github/get-modified-packages.php $(find src/Symfony -mindepth 2 -maxdepth 6 -type f -name composer.json -printf '%h\n' | grep -v src/Symfony/Component/Intl/Resources/emoji |jq -R -s -c 'split("\n")[:-1]') $(git diff --name-only origin/${{ github.base_ref }} HEAD | grep src/ | jq -R -s -c 'split("\n")[:-1]'))" - name: Verify meta files are correct run: | diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 771aae12a4f0f..6e866ce2e2c57 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -49,6 +49,8 @@ jobs: git checkout composer.json git checkout -m ${{ github.base_ref }} + # to be removed when psalm adds support for intersection types + sed -i 's/Uuid&/Uuid|/' src/Symfony/Component/Uid/Factory/TimeBasedUuidFactory.php ./vendor/bin/psalm.phar --set-baseline=.github/psalm/psalm.baseline.xml --no-progress git checkout -m FETCH_HEAD diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 0000000000000..7ee945d432674 --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,63 @@ +name: Scorecards supply-chain security + +on: + # Only the default branch is supported. + branch_protection_rule: + schedule: + - cron: '34 4 * * 6' + push: + branches: [ "6.2" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecards analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Used to receive a badge. (Upcoming feature) + id-token: write + # Needs for private repositories. + contents: read + actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # v3.0.0 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@3e15ea8318eee9b333819ec77a36aca8d39df13e # v1.1.1 + with: + results_file: results.sarif + results_format: sarif + # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecards on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} + + # Publish the results for public repositories to enable scorecard badges. For more details, see + # https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories, `publish_results` will automatically be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # v1.0.26 + with: + sarif_file: results.sarif diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 95811f790791e..4300d5bfc9015 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -21,7 +21,7 @@ jobs: name: Tests env: - extensions: amqp,apcu,igbinary,intl,mbstring,memcached,redis-5.3.4 + extensions: amqp,apcu,igbinary,intl,mbstring,memcached,redis strategy: matrix: @@ -93,7 +93,7 @@ jobs: echo SYMFONY_DEPRECATIONS_HELPER=weak >> $GITHUB_ENV cp composer.json composer.json.orig echo -e '{\n"require":{'"$(grep phpunit-bridge composer.json)"'"php":"*"},"minimum-stability":"dev"}' > composer.json - php .github/build-packages.php HEAD^ $SYMFONY_VERSION $(find src/Symfony -mindepth 2 -maxdepth 6 -type f -name composer.json -printf '%h\n') + php .github/build-packages.php HEAD^ $SYMFONY_VERSION $(find src/Symfony -mindepth 2 -maxdepth 6 -type f -name composer.json -printf '%h\n' | grep -v src/Symfony/Component/Intl/Resources/emoji) mv composer.json composer.json.phpunit mv composer.json.orig composer.json fi diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 2bc2c558d7b9b..db6a854c1f398 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -31,8 +31,10 @@ 'protected_to_private' => false, 'native_constant_invocation' => ['strict' => false], 'nullable_type_declaration_for_default_null_value' => ['use_nullable_type_declaration' => false], + 'no_superfluous_phpdoc_tags' => ['remove_inheritdoc' => true], 'header_comment' => ['header' => $fileHeaderComment], 'modernize_strpos' => true, + 'get_class_to_class_keyword' => true, ]) ->setRiskyAllowed(true) ->setFinder( @@ -64,6 +66,11 @@ ->notPath('Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php') // stop removing spaces on the end of the line in strings ->notPath('Symfony/Component/Messenger/Tests/Command/FailedMessagesShowCommandTest.php') + // auto-generated proxies + ->notPath('Symfony/Component/Cache/Traits/Redis5Proxy.php') + ->notPath('Symfony/Component/Cache/Traits/Redis6Proxy.php') + ->notPath('Symfony/Component/Cache/Traits/RedisCluster5Proxy.php') + ->notPath('Symfony/Component/Cache/Traits/RedisCluster6Proxy.php') ) ->setCacheFile('.php-cs-fixer.cache') ; diff --git a/CHANGELOG-6.0.md b/CHANGELOG-6.0.md index fdfff3311a2d5..6afc9fc5e27b7 100644 --- a/CHANGELOG-6.0.md +++ b/CHANGELOG-6.0.md @@ -7,6 +7,22 @@ in 6.0 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v6.0.0...v6.0.1 +* 6.0.14 (2022-10-12) + + * bug #47621 [Serializer] Allow getting discriminated type by class name (TamasSzigeti) + * bug #47833 [TwigBridge] Remove empty spaces between choices when using checkbox-inline or checkbox-switch (simondaigre) + * bug #47808 [HttpClient] Fix seeking in not-yet-initialized requests (nicolas-grekas) + * bug #47798 [DoctrineBridge] Fix auto mapping for bundles that contain only embeddables (jorissae) + * bug #47702 [Messenger] Fix default serializer not handling DateTime objects properly (barton-webwings) + * bug #47779 [Console] Fix `Helper::removeDecoration` hyperlink bug (greew) + * bug #47753 [Mime] sync message serializer code for forward-compatibility (xabbuh) + * bug #47763 [PropertyInfo] a readonly property must not be reported as being writable (xabbuh) + * bug #47731 [WebProfiler] Fix overflow issue in Forms panel (zolikonta) + * bug #46956 [FrameworkBundle] Allow to specify `null` for exception mapping configuration values (andrew-demb) + * bug #47746 [HttpFoundation] Fix BinaryFileResponse content type detection logic (X-Coder264) + * bug #47626 [Notifier] [Expo] Throw exception on error-response from expo api (sdrewergutland) + * bug #47317 [Security] Fix login url matching when app is not run with url rewriting or from a sub folder (sgehrig) + * 6.0.13 (2022-09-30) * bug #47637 [FrameworkBundle] Fix passing `serializer.default_context` option to normalizers (wuchen90) diff --git a/CHANGELOG-6.2.md b/CHANGELOG-6.2.md new file mode 100644 index 0000000000000..c1bcec9a2a782 --- /dev/null +++ b/CHANGELOG-6.2.md @@ -0,0 +1,176 @@ +CHANGELOG for 6.2.x +=================== + +This changelog references the relevant changes (bug and security fixes) done +in 6.2 minor versions. + +To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash +To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v6.2.0...v6.2.1 + +* 6.2.0-BETA1 (2022-10-24) + + * feature #47364 [DependencyInjection] Allow array attributes for service tags (aschempp) + * feature #44166 [Config] Use better typehint in PHP Configuration (jderusse) + * feature #47956 [Notifier] Add support for editing Telegram messages (chr-hertel) + * feature #43534 [Serializer] Add `SerializedPath` annotation to flatten nested attributes (boenner) + * feature #47943 [Config][Routing] Nicer config syntax for PSR-4 route loading (derrabus) + * feature #46224 [Form] Add `hash_property_path` option to `PasswordType` (Seb33300) + * feature #47950 [HttpClient] Add support for "friendsofphp/well-known-implementations" (nicolas-grekas) + * feature #47936 [HttpClient] Add `withOptions()` to `HttplugClient` and `Psr18Client` (nicolas-grekas) + * feature #46053 [Messenger] Add option `allow_no_senders` to enable throwing when a message doesn't have a sender (babeuloula) + * feature #45907 [SecurityBundle] Allow specifying attributes for `RequestMatcher` (freiondrej-lmc) + * feature #47483 [HttpKernel] Make Logger implement DebugLoggerInterface (MatTheCat) + * feature #46161 [Translation] Add `PhpAstExtractor` (welcoMattic) + * feature #47872 [Validator] new email validation option to match with w3c official specification (guillemfondin) + * feature #47916 [Routing] PSR-4 directory loader (derrabus) + * feature #47890 [SecurityBundle] Deprecate the `enable_authenticator_manager` option (chalasr) + * feature #47906 [DependencyInjection] Allow injecting the current env into php config closures (HypeMC) + * feature #47902 [DependencyInjection] Add support for tagged iterators/locators exclude option to xml and yaml (HypeMC) + * feature #47801 [DependencyInjection] Allow array for the value of Autowire attribute (willemverspyck) + * feature #47864 [DoctrineBridge] Deprecate calling `ContainerAwareEventManager::getListeners()` without event name (derrabus) + * feature #47711 [Mime] deprecate attach/embed methods in favor of Email::addPart() (fabpot) + * feature #47832 [HttpClient] Make HttplugClient implement PSR-17 factories instead of Httplug's (nicolas-grekas) + * feature #47817 [Security] TraceableAccessDecisionManager: fix inspecting voters of custom access decision managers (sarbanha) + * feature #47750 [Console] Show available commands in namespace when running namespace as command (wouterj) + * feature #47730 Ban DateTime from the codebase (WebMamba) + * feature #47496 [FrameworkBundle] Make the Router `cache_dir` configurable (mpdude) + * feature #47511 [Form][PropertyAccess] Allow optional property accesses (fsoedjede) + * feature #47068 [Messenger] Deprecate MessageHandlerInterface and MessageSubscriberInterface (alamirault) + * feature #47460 [Messenger] add dedicated method for disabling instead of passing boolean flags (xabbuh) + * feature #47643 [WebProfilerBundle] Update the mailer panel (javiereguiluz) + * feature #47710 [Validator] File: add option to check extension (dunglas) + * feature #47734 [Validator] add the getCause() to the ConstraintViolationInterface (xabbuh) + * feature #47308 [Console] Allow limiting the height of a console section (wouterj) + * feature #47243 Add context option to configure the indentation of nested nodes for `YamlEncoder` (dbu) + * feature #47462 [Mime] Simplify adding Parts to an Email (fabpot) + * feature #47683 [DependencyInjection] Deprecate numeric parameter names (HeahDude) + * feature #47377 [HttpKernel] Use Accept-Language header even if there are no enabled locales (MatTheCat) + * feature #47588 Add warning for possibly truncated inputs in QuestionHelper (#47586) (pbek) + * feature #47665 [WebProfilerBundle] [WebProfilerPanel] Update the configuration panel (javiereguiluz) + * feature #47630 [FrameworkBundle] Add semantic config for new terminate_on_cache_hit HttpCache option (wouterj) + * feature #47595 [HttpFoundation] Extract request matchers for better reusability (fabpot) + * feature #47535 [TwigBridge] Expose current route in `AppVariable` (HeahDude) + * feature #47536 [TwigBundle] add option for configuring custom HTML to text converter services (xabbuh) + * feature #46064 [Security] Add a `ChainUserChecker` to allow calling multiple user checkers for a firewall (mbabker) + * feature #47445 [FrameworkBundle] Allow secrets vaults to be used directly outside Symfony (AndreasA) + * feature #47148 [WebProfilerBundle] Profiler redesign (javiereguiluz) + * feature #38996 Remove the default values from setters with a nullable parameter (derrabus, nicolas-grekas) + * feature #42593 [Validator] Add the `When` constraint and validator (wuchen90) + * feature #47525 [Uid] Add UuidV7 and UuidV8 (nicolas-grekas) + * feature #47515 [Uid] Add MaxUuid and MaxUlid (nicolas-grekas) + * feature #47407 [Console] Terminal Color Mode refactoring and force Color Mode (julien-boudry) + * feature #47507 [Uid] Add interface for `getDateTime()` and apply to relevant UIDs (shrikeh) + * feature #47236 [DependencyInjection][VarExporter] Generate lazy-loading virtual proxies for non-ghostable lazy services (nicolas-grekas) + * feature #39622 [Messenger] Be able to get raw data when a message in not decodable by the PHP Serializer (lyrixx) + * feature #47311 [FrameworkBundle] Update ContainerDebugCommand to add parial search for tags (vshevelev, BOB41K1987) + * feature #47367 [DependencyInjection] Handle INI arrays (MatTheCat) + * feature #47373 [Notifier] Add Chatwork Notifier Bridge (Ippey) + * feature #47363 [HttpKernel] Replace ArgumentValueResolverInterface by ValueResolverInterface (nicolas-grekas) + * feature #47101 [DependencyInjection] Allow service subscribers to return `SubscribedService[]` (kbond) + * feature #40152 [Messenger] Pass sender details to SendMessageToTransportsEvent (Jeroeny) + * feature #41171 [Messenger] Add simple transport based rate limiter to Messenger (bobvandevijver) + * feature #47295 [PhpUnitBridge] add ability to mock the hrtime() function (xabbuh) + * feature #47264 [String] Add support for emoji in AsciiSlugger (lyrixx) + * feature #47263 [Intl] Update EmojiTransliterator to translate emoji to github and slack short code (lyrixx) + * feature #45418 [Messenger] Add HandlerArgumentsStamp (enumag) + * feature #47094 [HttpKernel] Use xxh128 algorithm instead of sha256 for http cache store key (Pascal Woerde) + * feature #46000 [Workflow] Mark registry as internal and deprecate the service (lyrixx) + * feature #46428 [Security] Access Token Authenticator (Spomky) + * feature #47225 [Mime] Re-allow addIdHeader to be used for 'In-Reply-To' and 'References' headers (AlbinoDrought) + * feature #47190 [Mailer] Add a way to change the Bus transport dynamically (fabpot) + * feature #47201 [Mime] Add a way to control the HTML to text conversion (fabpot) + * feature #47202 [Serializer] enable JSON_PRESERVE_ZERO_FRACTION by default (dbu) + * feature #39306 [Messenger] Add `TransportNamesStamp` to change the transport while dispatching a message (asilelik, fabpot) + * feature #47196 Allow extending `#[When]` attribute (ruudk) + * feature #47191 [Mailer] Add a way to inject Stamps when sending an email via Messenger (fabpot) + * feature #47170 [Mailer] Use better error code when auth fails (fabpot) + * feature #46978 [Security] Allow using expressions with the #[IsGranted] attribute (HypeMC) + * feature #46571 [Messenger] Add new `messenger:count` command that return a list of transports with their "to be processed" message count. (ktherage, ogizanagi, EXT - THERAGE Kevin) + * feature #43865 [TwigBridge] Add support for toggle buttons in Bootstrap 5 form theme (ker0x) + * feature #46683 [Ldap] Deprecate '{username}' parameter use in favour of '{user_identifier}' in LDAP configuration (EXT - THERAGE Kevin) + * feature #46514 [HttpKernel] Add option to render Surrogate fragment with absolute URIs (Kern046) + * feature #46715 [Clock] A new component to decouple applications from the system clock (nicolas-grekas) + * feature #42355 [HttpKernel] Bugfix/last modified response strategy (aschempp) + * feature #47080 [Mailer] Add new events (fabpot) + * feature #47075 [Mime] Change the way we avoid rendering an email twice (fabpot) + * feature #46755 [Intl] Add `EmojiTransliterator` to translate emoji to many locales (lyrixx, nicolas-grekas) + * feature #47062 [Console] Don't cut Urls wrapped in SymfonyStyle block (fchris82, GromNaN) + * feature #45987 [Notifier] Add `from` in `SmsMessage` (alamirault) + * feature #46142 [ExpressionLanguage] Add support for null coalescing syntax (mytuny) + * feature #47050 [Form] Allow TranslatableInterface to the FormType help option (alamirault) + * feature #46110 [RateLimiter][Security] Improve performance of login/request rate limiter (Seldaek, wouterj) + * feature #46895 [Notifier] Introduce PHPUnit constraints and assertions for the Notifier (ismail1432) + * feature #47049 [Mailer] Throw a more specific exception when a BodyRendererInterface is needed but not configured (fabpot) + * feature #47040 Add a mailer:test command (fabpot) + * feature #46242 [Console] Add support for resuming a ProgressBar (yivi) + * feature #46962 [Mime] Add DataPart::setContentId() (fabpot) + * feature #47038 [Notifier] Add Notification::exception() (fabpot) + * feature #46944 [Console] Add Ansi8 (256 color) support, improve true color (Ansi24) support detection (julien-boudry) + * feature #47034 [Mime] Simplify code (fabpot) + * feature #47018 [Console] Zsh shell autocompletion (adhocore, GromNaN) + * feature #46591 [Finder] Add methods to sort by extension & size (sandoba) + * feature #46126 [Finder] Case insensitive file sort (hmoreau) + * feature #45034 [HttpFoundation] Rename Request::getContentType to getContentTypeFormat (MarkPedron) + * feature #46806 [Cache][WebProfilerBundle] Add adapter class to Cache `DataCollector` (Jean-Beru) + * feature #44902 Add visibility context option in PropertyNormalizer (alamirault) + * feature #46567 [Security] [LoginLink] Set custom lifetime for login link (mbrodala, fabpot) + * feature #46599 Add "negate" option to Expression constraint (fmata) + * feature #46821 [FrameworkBundle] Add `resolve-env` option to debug:config command (alexandre-daubois) + * feature #46580 [SecurityBundle] Add shortcut option to enable logout CSRF protection (wouterj) + * feature #46814 [FrameworkBundle] Add service and alias deprecation message to debug:container output (94noni) + * feature #47008 [Messenger] Add options to `FailedMessagesShowCommand` (Florian Guimier, fabpot) + * feature #45977 [Validator] Add the match option to the Choice constraint (fancyweb) + * feature #46338 [Security] Allow configuring a target url when switching user (94noni) + * feature #46326 SMTP Transport to provide the (final) Message-ID if available (Raphaël Droz) + * feature #43854 [DoctrineBridge] Add an Entity Argument Resolver (jderusse, nicolas-grekas) + * feature #46315 [Mailer] `max_per_second` option configurable via DSN (gassan) + * feature #46118 [Security] Don't allow empty username or empty password (bikalbasnet) + * feature #46229 [Messenger] Make Redis messages countable (Jean-Beru) + * feature #41406 [Security] Add a method in the security helper to ease programmatic logout (johnkrovitch, chalasr) + * feature #45404 [Mailer] allow custom hosts for ses+smtp with amazon mailer (jrushlow) + * feature #45945 [Uid] Added toHexString method to AbstractUid class (aurimasniekis) + * feature #46642 [DoctrineBridge] Add `NAME` const for UID types (marcelsiegert) + * feature #46502 [Dotenv] Variable filter added to debug command (rmikalkenas) + * feature #46211 [Mailer] Add Infobip bridge (B-Galati) + * feature #46773 [VarDumper] Add `FFI\CData` and `FFI\CType` types (SerafimArts) + * feature #46946 [Form] ChoiceType choices must support TranslatableInterface (alamirault) + * feature #38903 [FrameworkBundle] Add "Usages" to debug:container output (Bert ter Heide, bertterheide) + * feature #46901 [Console] Be explicit about the completion API version (wouterj) + * feature #46752 [DependencyInjection] Use lazy-loading ghost object proxies out of the box (nicolas-grekas) + * feature #46880 [HttpKernel] Add `#[Cache()]` to describe the default HTTP cache headers on controllers (nicolas-grekas) + * feature #46751 [VarExporter] Add trait to help implement lazy loading ghost objects (nicolas-grekas) + * feature #46906 [TwigBridge] Add `#[Template()]` to describe how to render arrays returned by controllers (nicolas-grekas) + * feature #46907 [Security] Add `#[IsGranted()]` (nicolas-grekas) + * feature #46183 Hide sensitive information with `SensitiveParameter` attribute (GromNaN) + * feature #46896 Console/SymfonyStyle: Add Multiselect to choice() (julien-boudry) + * feature #46883 [DependencyInjection] Add `shuffle` env processor (ostrolucky) + * feature #46846 [Notifier] Add Zendesk Notifier Bridge (stloyd) + * feature #46001 [HttpKernel] Add `ControllerEvent::getAttributes()` to handle attributes on controllers (nicolas-grekas) + * feature #46854 [FrameworkBundle] Make `AbstractController::render()` able to deal with forms and deprecate `renderForm()` (nicolas-grekas) + * feature #41274 [Security] Add a method in the security helper to ease programmatic login (#40662) (johnkrovitch, chalasr) + * feature #46831 Add deprecation when the session is not FlashBagAware (VincentLanglet) + * feature #46491 Introduce FlashBagAwareSessionInterface (VincentLanglet) + * feature #46813 [Form] Provide string keys when iterating on a form (VincentLanglet) + * feature #46680 [Serializer] Provide context information from attribute for promoted properties (DanielBadura) + * feature #46564 [DependencyInjection] Add Enum Env Var Processor (jack-worman) + * feature #46763 [HttpCache] Do not call terminate() on cache hit (Toflar) + * feature #45997 [FrameworkBundle][HttpKernel] Add deprecation warning to show `HttpKernel::handle()` will catch throwables (Nyholm) + * feature #46714 [Mailer] Deprecate OhMySmtp Transport, Create MailPace transport (Holicz) + * feature #46771 [Yaml] Add support for `!php/enum *->value` syntax (nicolas-grekas) + * feature #46395 [Notifier] Add Contact Everyone Bridge (franckranaivo) + * feature #46724 [Notifier] Add SMSFactor bridge (Gwemox) + * feature #46741 [DependencyInjection] Allow using ghost objects for lazy loading services (nicolas-grekas) + * feature #46675 [Serializer] Add support of true built-in type (from PHP 8.2) (bobahvas, alexandre-daubois) + * feature #46663 [Serializer] Deprecate autowiring aliases pointing to concrete normalizers (chalasr) + * feature #46584 [Security] Enforce maximum username length in UserBadge (wouterj) + * feature #46066 [Security] Add an easier way to get the current firewall configuration (Kocal) + * feature #46614 Remove Debug component leftovers (chalasr) + * feature #46566 [Serializer][WebProfilerBundle] Show serializer collector info in toolbar (ogizanagi) + * feature #46569 [Serializer][WebProfilerBundle] Collect & show caller source code (ogizanagi) + * feature #46094 [Security][SecurityBundle] Move the `Security` helper to SecurityBundle (chalasr) + * feature #46518 [Validator] deprecate the loose e-mail validation mode (xabbuh) + * feature #45985 [TwigBridge] Add form_label_content and form_help_content block to form_div_layout (alexander-schranz) + * feature #46430 [Routing] Add `Requirement::POSITIVE_INT` for common ids and pagination (HeahDude) + * feature #46279 [DependencyInjection] Optimize autowiring logic by telling it about excluded symbols (nicolas-grekas) + * feature #46452 [DependencyInjection] Add Hydrator::hydrate() and preserve PHP references when using it (nicolas-grekas) + diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d652d3de50a5c..baf049c540d1d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -90,21 +90,21 @@ The Symfony Connect username in parenthesis allows to get more information - Florin Patan (florinpatan) - Peter Rehm (rpet) - Henrik Bjørnskov (henrikbjorn) + - David Buchmann (dbu) - Konstantin Myakshin (koc) - Andrej Hudec (pulzarraider) - Julien Falque (julienfalque) - Massimiliano Arione (garak) - Douglas Greenshields (shieldo) - - David Buchmann (dbu) - Christian Raue - Jáchym Toušek (enumag) + - Mathias Arlaud (mtarld) - Graham Campbell (graham) - Michel Weimerskirch (mweimerskirch) - Eric Clemmons (ericclemmons) - Issei Murasawa (issei_m) - Fran Moreno (franmomu) - Malte Schlüter (maltemaltesich) - - Mathias Arlaud (mtarld) - Vasilij Dusko - Denis (yethee) - Arnout Boks (aboks) @@ -122,12 +122,12 @@ The Symfony Connect username in parenthesis allows to get more information - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) + - Antoine Lamirault - Daniel Holmes (dholmes) - Toni Uebernickel (havvg) - Bart van den Burg (burgov) - Jordan Alliot (jalliot) - Smaine Milianni (ismail1432) - - Antoine Lamirault - John Wards (johnwards) - Dariusz Ruminski - Lars Strojny (lstrojny) @@ -210,6 +210,7 @@ The Symfony Connect username in parenthesis allows to get more information - Martin Hujer (martinhujer) - Wouter J - Guilliam Xavier + - Antonio Pauletich (x-coder264) - Timo Bakx (timobakx) - Juti Noppornpitak (shiroyuki) - Joe Bennett (kralos) @@ -226,7 +227,6 @@ The Symfony Connect username in parenthesis allows to get more information - Arnaud Kleinpeter (nanocom) - Guilherme Blanco (guilhermeblanco) - Chi-teck - - Antonio Pauletich (x-coder264) - Michael Voříšek - SpacePossum - Pablo Godel (pgodel) @@ -479,6 +479,7 @@ The Symfony Connect username in parenthesis allows to get more information - Quynh Xuan Nguyen (seriquynh) - Ray - Philipp Cordes (corphi) + - Andrii Dembitskyi - Chekote - bhavin (bhavin4u) - Pavel Popov (metaer) @@ -575,7 +576,6 @@ The Symfony Connect username in parenthesis allows to get more information - Adrian Rudnik (kreischweide) - Pavel Batanov (scaytrase) - Francesc Rosàs (frosas) - - Andrii Dembitskyi - Bongiraud Dominique - janschoenherr - Marko Kaznovac (kaznovac) @@ -642,6 +642,7 @@ The Symfony Connect username in parenthesis allows to get more information - Vitaliy Zakharov (zakharovvi) - Tobias Sjösten (tobiassjosten) - Gyula Sallai (salla) + - Stefan Gehrig (sgehrig) - Benjamin Cremer (bcremer) - rtek - Inal DJAFAR (inalgnu) @@ -870,7 +871,6 @@ The Symfony Connect username in parenthesis allows to get more information - Dirk Pahl (dirkaholic) - Cédric Lombardot (cedriclombardot) - Jonas Flodén (flojon) - - Stefan Gehrig (sgehrig) - Adrien Lucas (adrienlucas) - Dominik Zogg - Kai Dederichs @@ -1037,6 +1037,7 @@ The Symfony Connect username in parenthesis allows to get more information - Quentin Dequippe (qdequippe) - Yewhen Khoptynskyi (khoptynskyi) - Jérôme Nadaud (jnadaud) + - wuchen90 - Alexandre Tranchant (alexandre_t) - Anthony Moutte - shreyadenny @@ -1050,6 +1051,7 @@ The Symfony Connect username in parenthesis allows to get more information - Grégoire Hébert (gregoirehebert) - alcaeus - Fred Cox + - Matheo Daninos (mathdns) - Iliya Miroslavov Iliev (i.miroslavov) - Safonov Nikita (ns3777k) - Simon DELICATA @@ -1975,7 +1977,6 @@ The Symfony Connect username in parenthesis allows to get more information - Kirill Nesmeyanov (serafim) - Reece Fowell (reecefowell) - Guillaume Gammelin - - wuchen90 - Valérian Galliat - d-ph - Renan Taranto (renan-taranto) @@ -2051,6 +2052,7 @@ The Symfony Connect username in parenthesis allows to get more information - boite - Silvio Ginter - MGDSoft + - joris - Vadim Tyukov (vatson) - David Wolter (davewww) - Sortex @@ -2099,6 +2101,7 @@ The Symfony Connect username in parenthesis allows to get more information - Thomas - Norbert Schultheisz - Maximilian Berghoff (electricmaxxx) + - SOEDJEDE Felix (fsoedjede) - Piotr Antosik (antek88) - Nacho Martin (nacmartin) - Sergey Novikov (s12v) @@ -2225,6 +2228,7 @@ The Symfony Connect username in parenthesis allows to get more information - Nicolas Fabre (nfabre) - Raul Rodriguez (raul782) - mshavliuk + - Jesper Skytte - MightyBranch - Kacper Gunia (cakper) - Derek Lambert (dlambert) @@ -2413,6 +2417,7 @@ The Symfony Connect username in parenthesis allows to get more information - Daniel González Zaballos (dem3trio) - Emmanuel Vella (emmanuel.vella) - Guillaume BRETOU (guiguiboy) + - Ibon Conesa (ibonkonesa) - nuryagdy mustapayev (nueron) - Carsten Nielsen (phreaknerd) - Jay Severson @@ -2598,6 +2603,7 @@ The Symfony Connect username in parenthesis allows to get more information - Andrew Coulton - Ulugbek Miniyarov - Jeremy Benoist + - sdrewergutland - Michal Gebauer - Phil Davis - Gleb Sidora @@ -2635,6 +2641,7 @@ The Symfony Connect username in parenthesis allows to get more information - Mike Francis - Nil Borodulia - Almog Baku (almogbaku) + - Benjamin Schultz (bschultz) - Gerd Christian Kunze (derdu) - Ionel Scutelnicu (ionelscutelnicu) - Kamil Madejski (kmadejski) @@ -2801,6 +2808,7 @@ The Symfony Connect username in parenthesis allows to get more information - David Christmann - root - pf + - Zoli Konta - Vincent Chalnot - Patrizio Bekerle - Tom Maguire @@ -2917,7 +2925,6 @@ The Symfony Connect username in parenthesis allows to get more information - Kevin Verschaeve (keversc) - Kevin Herrera (kherge) - Luis Ramón López López (lrlopez) - - Matheo Daninos (mathdns) - Mehdi Mabrouk (mehdidev) - Bart Reunes (metalarend) - Muriel (metalmumu) @@ -2936,6 +2943,7 @@ The Symfony Connect username in parenthesis allows to get more information - Lajos Veres (vlajos) - Vladimir Chernyshev (volch) - Yorkie Chadwick (yorkie76) + - Pavel Barton - GuillaumeVerdon - ureimers - akimsko @@ -3203,6 +3211,7 @@ The Symfony Connect username in parenthesis allows to get more information - emilienbouard (neime) - Nicholas Byfleet (nickbyfleet) - Nicolas Bondoux (nsbx) + - Cedric Kastner (nurtext) - ollie harridge (ollietb) - Pawel Szczepanek (pauluz) - Philippe Degeeter (pdegeeter) diff --git a/README.md b/README.md index 31b8b7c62a75f..9882d80116fb8 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,19 @@ Installation Sponsor ------- -Symfony 6.1 is [backed][27] by [basecom][28]. +Symfony 6.2 is [backed][27] by [Les-Tilleuls.coop][28] and [Sulu][29]. -As a professional software service provider, basecom implements customized -solutions in the areas of e-commerce, PIM solutions and web portals. With our -experience and certified expertise, we have been one of the most renowned -Symfony specialists in Germany for many years. +**Les-Tilleuls.coop** is a team of 50+ Symfony experts who can help you design, +develop and fix your projects. We provide a wide range of professional services +including development, consulting, coaching, training and audits. We also are +highly skilled in JS, Go and DevOps. We are a worker cooperative! -Help Symfony by [sponsoring][29] its development! +**Sulu** is the CMS for Symfony developers. It provides pre-built content-management +features while giving developers the freedom to build, deploy, and maintain +custom solutions using full-stack Symfony. Sulu is ideal for creating complex +websites, integrating external tools, and building custom-built solutions. +Help Symfony by [sponsoring][30] its development! Documentation ------------- @@ -88,5 +92,6 @@ and supported by [Symfony contributors][19]. [25]: https://symfony.com/doc/current/contributing/code_of_conduct/care_team.html [26]: https://symfony.com/book [27]: https://symfony.com/backers -[28]: https://www.basecom.de/ -[29]: https://symfony.com/sponsor +[28]: https://les-tilleuls.coop/ +[29]: https://sulu.io/ +[30]: https://symfony.com/sponsor diff --git a/UPGRADE-6.2.md b/UPGRADE-6.2.md new file mode 100644 index 0000000000000..c803677a28272 --- /dev/null +++ b/UPGRADE-6.2.md @@ -0,0 +1,130 @@ +UPGRADE FROM 6.1 to 6.2 +======================= + +Config +------ + + * Deprecate calling `NodeBuilder::setParent()` without any arguments + +Console +------- + + * Deprecate calling `*Command::setApplication()`, `*FormatterStyle::setForeground/setBackground()`, `Helper::setHelpSet()`, `Input*::setDefault()`, `Question::setAutocompleterCallback/setValidator()`without any arguments + * Change the signature of `OutputFormatterStyleInterface::setForeground/setBackground()` to `setForeground/setBackground(?string)` + * Change the signature of `HelperInterface::setHelperSet()` to `setHelperSet(?HelperSet)` + +DependencyInjection +------------------- + + * Change the signature of `ContainerAwareInterface::setContainer()` to `setContainer(?ContainerInterface)` + * Deprecate calling `ContainerAwareTrait::setContainer()` without arguments + * Deprecate using numeric parameter names + +Form +---- + + * Deprecate calling `Button/Form::setParent()`, `ButtonBuilder/FormConfigBuilder::setDataMapper()`, `TransformationFailedException::setInvalidMessage()` without arguments + * Change the signature of `FormConfigBuilderInterface::setDataMapper()` to `setDataMapper(?DataMapperInterface)` + * Change the signature of `FormInterface::setParent()` to `setParent(?self)` + +FrameworkBundle +--------------- + + * Deprecate the `Symfony\Component\Serializer\Normalizer\ObjectNormalizer` and + `Symfony\Component\Serializer\Normalizer\PropertyNormalizer` autowiring aliases, type-hint against + `Symfony\Component\Serializer\Normalizer\NormalizerInterface` or implement `NormalizerAwareInterface` instead + * Deprecate `AbstractController::renderForm()`, use `render()` instead + * Deprecate `FrameworkExtension::registerRateLimiter()` + +HttpFoundation +-------------- + + * Deprecate `Request::getContentType()`, use `Request::getContentTypeFormat()` instead + * Deprecate calling `JsonResponse::setCallback()`, `Response::setExpires/setLastModified/setEtag()`, `MockArraySessionStorage/NativeSessionStorage::setMetadataBag()`, `NativeSessionStorage::setSaveHandler()` without arguments + +HttpClient +---------- + + * Deprecate implementing `Http\Message\RequestFactory`, `StreamFactory` and `UriFactory` on `HttplugClient` + +HttpKernel +---------- + + * Deprecate `ArgumentValueResolverInterface`, use `ValueResolverInterface` instead + * Deprecate calling `ConfigDataCollector::setKernel()`, `RouterListener::setCurrentRequest()` without arguments + +Ldap +---- + + * Deprecate `{username}` parameter use in favour of `{user_identifier}` + +Mailer +------ + + * Deprecate the `OhMySMTP` transport, use `MailPace` instead + +Mime +---- + + * Deprecate calling `Message::setBody()` without arguments + +PropertyAccess +-------------- + + * Deprecate calling `PropertyAccessorBuilder::setCacheItemPool()` without arguments + * Implementing the `PropertyPathInterface` without implementing the `isNullSafe()` method is deprecated + +Messenger +-------- + +* Deprecate `MessageHandlerInterface` and `MessageSubscriberInterface`, use the `AsMessageHandler` attribute instead + +Security +-------- + + * Add maximum username length enforcement of 4096 characters in `UserBadge` to + prevent [session storage flooding](https://symfony.com/blog/cve-2016-4423-large-username-storage-in-session) + * Deprecate the `Symfony\Component\Security\Core\Security` class and service, use `Symfony\Bundle\SecurityBundle\Security\Security` instead + * Passing empty username or password parameter when using `JsonLoginAuthenticator` is not supported anymore + * Add `$lifetime` parameter to `LoginLinkHandlerInterface::createLoginLink()` + * Change the signature of `TokenStorageInterface::setToken()` to `setToken(?TokenInterface $token)` + * Deprecate calling `TokenStorage::setToken()` or `UsageTrackingTokenStorage::setToken()` without arguments + +SecurityBundle +-------------- + + * Deprecate the `security.enable_authenticator_manager` config option + +Serializer +---------- + + * Deprecate calling `AttributeMetadata::setSerializedName()`, `ClassMetadata::setClassDiscriminatorMapping()` without arguments + * Change the signature of `AttributeMetadataInterface::setSerializedName()` to `setSerializedName(?string)` + * Change the signature of `ClassMetadataInterface::setClassDiscriminatorMapping()` to `setClassDiscriminatorMapping(?ClassDiscriminatorMapping)` + +Translation +----------- + + * Deprecate `PhpExtractor` in favor of `PhpAstExtractor` + * Add `PhpAstExtractor` (requires [nikic/php-parser](https://github.com/nikic/php-parser) to be installed) + +Validator +--------- + + * Deprecate the `loose` e-mail validation mode, use `html5` instead + +VarDumper +--------- + + * Deprecate calling `VarDumper::setHandler()` without arguments + +Workflow +-------- + + * The `Registry` is marked as internal and should not be used directly. use a tagged locator instead + ``` + tagged_locator('workflow', 'name') + ``` + * The first argument of `WorkflowDumpCommand` should be a `ServiceLocator` of + all workflows indexed by names + * Deprecate calling `Definition::setInitialPlaces()` without arguments diff --git a/UPGRADE-7.0.md b/UPGRADE-7.0.md new file mode 100644 index 0000000000000..587796bbb6e9f --- /dev/null +++ b/UPGRADE-7.0.md @@ -0,0 +1,8 @@ +UPGRADE FROM 6.4 to 7.0 +======================= + +Workflow +-------- + + * The first argument of `WorkflowDumpCommand` must be a `ServiceLocator` of all + workflows indexed by names diff --git a/composer.json b/composer.json index f2c15b2ac2fc9..eda77e963e5ef 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "composer-runtime-api": ">=2.1", "ext-xml": "*", "friendsofphp/proxy-manager-lts": "^1.0.2", - "doctrine/event-manager": "~1.0", + "doctrine/event-manager": "^1.2|^2", "doctrine/persistence": "^2|^3", "twig/twig": "^2.13|^3.0.4", "psr/cache": "^2.0|^3.0", @@ -57,6 +57,7 @@ "symfony/asset": "self.version", "symfony/browser-kit": "self.version", "symfony/cache": "self.version", + "symfony/clock": "self.version", "symfony/config": "self.version", "symfony/console": "self.version", "symfony/css-selector": "self.version", @@ -125,22 +126,23 @@ "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13.1|^3.0", "doctrine/orm": "^2.7.4", + "egulias/email-validator": "^2.1.10|^3.1", "guzzlehttp/promises": "^1.4", + "league/html-to-markdown": "^5.0", "masterminds/html5": "^2.7.2", "monolog/monolog": "^1.25.1|^2", "nyholm/psr7": "^1.0", "pda/pheanstalk": "^4.0", "php-http/httplug": "^1.0|^2.0", + "phpdocumentor/reflection-docblock": "^5.2", "phpstan/phpdoc-parser": "^1.0", "predis/predis": "~1.1", "psr/http-client": "^1.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "egulias/email-validator": "^2.1.10|^3.1", "symfony/mercure-bundle": "^0.3", "symfony/phpunit-bridge": "^5.4|^6.0", "symfony/runtime": "self.version", "symfony/security-acl": "~2.8|~3.0", - "phpdocumentor/reflection-docblock": "^5.2", "twig/cssinliner-extra": "^2.12|^3", "twig/inky-extra": "^2.12|^3", "twig/markdown-extra": "^2.12|^3" @@ -154,7 +156,6 @@ "masterminds/html5": "<2.6", "phpdocumentor/reflection-docblock": "<5.2", "phpdocumentor/type-resolver": "<1.5.1", - "ocramius/proxy-manager": "<2.1", "phpunit/phpunit": "<5.4.3" }, "config": { @@ -192,7 +193,7 @@ "url": "src/Symfony/Contracts", "options": { "versions": { - "symfony/contracts": "3.1.x-dev" + "symfony/contracts": "3.2.x-dev" } } }, diff --git a/psalm.xml b/psalm.xml index 3fb94145699cf..7a895615e7457 100644 --- a/psalm.xml +++ b/psalm.xml @@ -14,6 +14,7 @@ + @@ -35,5 +36,15 @@ + + + + + + + + + + diff --git a/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php b/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php new file mode 100644 index 0000000000000..5794d9814215d --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\ArgumentResolver; + +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\NoResultException; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager; +use Symfony\Bridge\Doctrine\Attribute\MapEntity; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Yields the entity matching the criteria provided in the route. + * + * @author Fabien Potencier + * @author Jérémy Derussé + */ +final class EntityValueResolver implements ValueResolverInterface +{ + public function __construct( + private ManagerRegistry $registry, + private ?ExpressionLanguage $expressionLanguage = null, + private MapEntity $defaults = new MapEntity(), + ) { + } + + public function resolve(Request $request, ArgumentMetadata $argument): array + { + $options = $this->getMapEntityAttribute($argument); + if (!$options->class || $options->disabled) { + return []; + } + if (!$manager = $this->getManager($options->objectManager, $options->class)) { + return []; + } + + $message = ''; + if (null !== $options->expr) { + if (null === $object = $this->findViaExpression($manager, $request, $options)) { + $message = sprintf(' The expression "%s" returned null.', $options->expr); + } + // find by identifier? + } elseif (false === $object = $this->find($manager, $request, $options, $argument->getName())) { + // find by criteria + if (!$criteria = $this->getCriteria($request, $options, $manager)) { + return []; + } + try { + $object = $manager->getRepository($options->class)->findOneBy($criteria); + } catch (NoResultException|ConversionException) { + $object = null; + } + } + + if (null === $object && !$argument->isNullable()) { + throw new NotFoundHttpException(sprintf('"%s" object not found by "%s".', $options->class, self::class).$message); + } + + return [$object]; + } + + private function getManager(?string $name, string $class): ?ObjectManager + { + if (null === $name) { + return $this->registry->getManagerForClass($class); + } + + try { + $manager = $this->registry->getManager($name); + } catch (\InvalidArgumentException) { + return null; + } + + return $manager->getMetadataFactory()->isTransient($class) ? null : $manager; + } + + private function find(ObjectManager $manager, Request $request, MapEntity $options, string $name): false|object|null + { + if ($options->mapping || $options->exclude) { + return false; + } + + $id = $this->getIdentifier($request, $options, $name); + if (false === $id || null === $id) { + return $id; + } + + if ($options->evictCache && $manager instanceof EntityManagerInterface) { + $cacheProvider = $manager->getCache(); + if ($cacheProvider && $cacheProvider->containsEntity($options->class, $id)) { + $cacheProvider->evictEntity($options->class, $id); + } + } + + try { + return $manager->getRepository($options->class)->find($id); + } catch (NoResultException|ConversionException) { + return null; + } + } + + private function getIdentifier(Request $request, MapEntity $options, string $name): mixed + { + if (\is_array($options->id)) { + $id = []; + foreach ($options->id as $field) { + // Convert "%s_uuid" to "foobar_uuid" + if (str_contains($field, '%s')) { + $field = sprintf($field, $name); + } + + $id[$field] = $request->attributes->get($field); + } + + return $id; + } + + if (null !== $options->id) { + $name = $options->id; + } + + if ($request->attributes->has($name)) { + return $request->attributes->get($name) ?? ($options->stripNull ? false : null); + } + + if (!$options->id && $request->attributes->has('id')) { + return $request->attributes->get('id') ?? ($options->stripNull ? false : null); + } + + return false; + } + + private function getCriteria(Request $request, MapEntity $options, ObjectManager $manager): array + { + if (null === $mapping = $options->mapping) { + $mapping = $request->attributes->keys(); + } + + if ($mapping && \is_array($mapping) && array_is_list($mapping)) { + $mapping = array_combine($mapping, $mapping); + } + + foreach ($options->exclude as $exclude) { + unset($mapping[$exclude]); + } + + if (!$mapping) { + return []; + } + + // if a specific id has been defined in the options and there is no corresponding attribute + // return false in order to avoid a fallback to the id which might be of another object + if (\is_string($options->id) && null === $request->attributes->get($options->id)) { + return []; + } + + $criteria = []; + $metadata = $manager->getClassMetadata($options->class); + + foreach ($mapping as $attribute => $field) { + if (!$metadata->hasField($field) && (!$metadata->hasAssociation($field) || !$metadata->isSingleValuedAssociation($field))) { + continue; + } + + $criteria[$field] = $request->attributes->get($attribute); + } + + if ($options->stripNull) { + $criteria = array_filter($criteria, static fn ($value) => null !== $value); + } + + return $criteria; + } + + private function findViaExpression(ObjectManager $manager, Request $request, MapEntity $options): ?object + { + if (!$this->expressionLanguage) { + throw new \LogicException(sprintf('You cannot use the "%s" if the ExpressionLanguage component is not available. Try running "composer require symfony/expression-language".', __CLASS__)); + } + + $repository = $manager->getRepository($options->class); + $variables = array_merge($request->attributes->all(), ['repository' => $repository]); + + try { + return $this->expressionLanguage->evaluate($options->expr, $variables); + } catch (NoResultException|ConversionException) { + return null; + } + } + + private function getMapEntityAttribute(ArgumentMetadata $argument): MapEntity + { + /** @var MapEntity $options */ + $options = $argument->getAttributes(MapEntity::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? $this->defaults; + + return $options->withDefaults($this->defaults, $argument->getType()); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php b/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php new file mode 100644 index 0000000000000..74caf14c9af55 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Attribute; + +/** + * Indicates that a controller argument should receive an Entity. + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class MapEntity +{ + public function __construct( + public ?string $class = null, + public ?string $objectManager = null, + public ?string $expr = null, + public ?array $mapping = null, + public ?array $exclude = null, + public ?bool $stripNull = null, + public array|string|null $id = null, + public ?bool $evictCache = null, + public bool $disabled = false, + ) { + } + + public function withDefaults(self $defaults, ?string $class): static + { + $clone = clone $this; + $clone->class ??= class_exists($class ?? '') ? $class : null; + $clone->objectManager ??= $defaults->objectManager; + $clone->expr ??= $defaults->expr; + $clone->mapping ??= $defaults->mapping; + $clone->exclude ??= $defaults->exclude ?? []; + $clone->stripNull ??= $defaults->stripNull ?? false; + $clone->id ??= $defaults->id; + $clone->evictCache ??= $defaults->evictCache ?? false; + + return $clone; + } +} diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 7c3e2c010b2a5..b313594e4ec6a 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +6.2 +--- + + * Add `#[MapEntity]` with its corresponding `EntityValueResolver` + * Add `NAME` constant to `UlidType` and `UuidType` + 6.0 --- diff --git a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php index 861510150b93c..0c107c066bac4 100644 --- a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php +++ b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php @@ -40,8 +40,6 @@ public function isOptional(): bool } /** - * {@inheritdoc} - * * @return string[] A list of files to preload on PHP 7.4+ */ public function warmUp(string $cacheDir): array diff --git a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php index d262c5e3591e4..bf337bf751635 100644 --- a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php +++ b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php @@ -44,9 +44,6 @@ public function __construct(ContainerInterface $container, array $subscriberIds $this->subscribers = $subscriberIds; } - /** - * {@inheritdoc} - */ public function dispatchEvent($eventName, EventArgs $eventArgs = null): void { if (!$this->initializedSubscribers) { @@ -68,21 +65,29 @@ public function dispatchEvent($eventName, EventArgs $eventArgs = null): void } /** - * {@inheritdoc} - * * @return object[][] */ public function getListeners($event = null): array { + if (null === $event) { + trigger_deprecation('symfony/doctrine-bridge', '6.2', 'Calling "%s()" without an event name is deprecated. Call "getAllListeners()" instead.', __METHOD__); + + return $this->getAllListeners(); + } if (!$this->initializedSubscribers) { $this->initializeSubscribers(); } - if (null !== $event) { - if (!isset($this->initialized[$event])) { - $this->initializeListeners($event); - } + if (!isset($this->initialized[$event])) { + $this->initializeListeners($event); + } + + return $this->listeners[$event]; + } - return $this->listeners[$event]; + public function getAllListeners(): array + { + if (!$this->initializedSubscribers) { + $this->initializeSubscribers(); } foreach ($this->listeners as $event => $listeners) { @@ -94,9 +99,6 @@ public function getListeners($event = null): array return $this->listeners; } - /** - * {@inheritdoc} - */ public function hasListeners($event): bool { if (!$this->initializedSubscribers) { @@ -106,9 +108,6 @@ public function hasListeners($event): bool return isset($this->listeners[$event]) && $this->listeners[$event]; } - /** - * {@inheritdoc} - */ public function addEventListener($events, $listener): void { if (!$this->initializedSubscribers) { @@ -130,9 +129,6 @@ public function addEventListener($events, $listener): void } } - /** - * {@inheritdoc} - */ public function removeEventListener($events, $listener): void { if (!$this->initializedSubscribers) { diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index c5318414dbf68..6cc881dc92091 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -53,9 +53,6 @@ public function addLogger(string $name, DebugStack $logger) $this->loggers[$name] = $logger; } - /** - * {@inheritdoc} - */ public function collect(Request $request, Response $response, \Throwable $exception = null) { $this->data = [ @@ -132,17 +129,11 @@ public function getTime() return $time; } - /** - * {@inheritdoc} - */ public function getName(): string { return 'db'; } - /** - * {@inheritdoc} - */ protected function getCasters(): array { return parent::getCasters() + [ diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/ObjectParameter.php b/src/Symfony/Bridge/Doctrine/DataCollector/ObjectParameter.php index 549a6af8bb42a..384ba0efeb869 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/ObjectParameter.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/ObjectParameter.php @@ -23,7 +23,7 @@ public function __construct(object $object, ?\Throwable $error) $this->object = $object; $this->error = $error; $this->stringable = \is_callable([$object, '__toString']); - $this->class = \get_class($object); + $this->class = $object::class; } public function getObject(): object diff --git a/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php b/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php index 98acccda50ba9..95dc8825ed213 100644 --- a/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php +++ b/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php @@ -32,9 +32,6 @@ public function __construct(ContainerInterface $container) $this->container = $container; } - /** - * {@inheritdoc} - */ public function addFixture(FixtureInterface $fixture) { if ($fixture instanceof ContainerAwareInterface) { diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php index 92985d89ca4ca..aa76d7d2da8e5 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php @@ -28,9 +28,6 @@ public function __construct(string $managerType) $this->managerType = $managerType; } - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { $this->updateValidatorMappingFiles($container, 'xml', 'xml'); diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index 74a3d3200f05b..30d0fb37ced80 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -53,9 +53,6 @@ public function __construct(string $connectionsParameter, string $managerTemplat $this->tagPrefix = $tagPrefix; } - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { if (!$container->hasParameter($this->connectionsParameter)) { diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php index 95a74533ba0f5..1ea0470bf8d0b 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterUidTypePass.php @@ -19,9 +19,6 @@ final class RegisterUidTypePass implements CompilerPassInterface { - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { if (!class_exists(AbstractUid::class)) { diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php index e191bea3ca16d..30ea98880887d 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php @@ -50,9 +50,6 @@ public function __construct(ObjectManager $manager, string $class, IdReader $idR $this->objectLoader = $objectLoader; } - /** - * {@inheritdoc} - */ protected function loadChoices(): iterable { return $this->objectLoader diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index 3364ffa4c4edb..7381a6a992679 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -38,17 +38,11 @@ public function __construct(QueryBuilder $queryBuilder) $this->queryBuilder = $queryBuilder; } - /** - * {@inheritdoc} - */ public function getEntities(): array { return $this->queryBuilder->getQuery()->execute(); } - /** - * {@inheritdoc} - */ public function getEntitiesByIds(string $identifier, array $values): array { if (null !== $this->queryBuilder->getMaxResults() || 0 < (int) $this->queryBuilder->getFirstResult()) { diff --git a/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php b/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php index d5dc1a23b72a6..61fc5f8c6e72b 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php +++ b/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php @@ -18,6 +18,8 @@ /** * @author Bernhard Schussek + * + * @implements DataTransformerInterface */ class CollectionToArrayTransformer implements DataTransformerInterface { diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index f2e65a7f0a8c0..1847e50eeef28 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -44,9 +44,6 @@ public function __construct(ManagerRegistry $registry) $this->registry = $registry; } - /** - * {@inheritdoc} - */ public function guessType(string $class, string $property): ?TypeGuess { if (!$ret = $this->getMetadata($class)) { @@ -87,9 +84,6 @@ public function guessType(string $class, string $property): ?TypeGuess }; } - /** - * {@inheritdoc} - */ public function guessRequired(string $class, string $property): ?ValueGuess { $classMetadatas = $this->getMetadata($class); @@ -127,9 +121,6 @@ public function guessRequired(string $class, string $property): ?ValueGuess return null; } - /** - * {@inheritdoc} - */ public function guessMaxLength(string $class, string $property): ?ValueGuess { $ret = $this->getMetadata($class); @@ -148,9 +139,6 @@ public function guessMaxLength(string $class, string $property): ?ValueGuess return null; } - /** - * {@inheritdoc} - */ public function guessPattern(string $class, string $property): ?ValueGuess { $ret = $this->getMetadata($class); diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index d50ca5c339b95..fb45f99ac2657 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -57,9 +57,6 @@ public function getLoader(ObjectManager $manager, object $queryBuilder, string $ return new ORMQueryBuilderLoader($queryBuilder); } - /** - * {@inheritdoc} - */ public function getBlockPrefix(): string { return 'entity'; diff --git a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php index b4cc571bb56fc..f715e782e3af7 100644 --- a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +++ b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php @@ -32,9 +32,6 @@ public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch $this->stopwatch = $stopwatch; } - /** - * {@inheritdoc} - */ public function startQuery($sql, array $params = null, array $types = null): void { $this->stopwatch?->start('doctrine', 'doctrine'); @@ -44,9 +41,6 @@ public function startQuery($sql, array $params = null, array $types = null): voi } } - /** - * {@inheritdoc} - */ public function stopQuery(): void { $this->stopwatch?->stop('doctrine'); diff --git a/src/Symfony/Bridge/Doctrine/ManagerRegistry.php b/src/Symfony/Bridge/Doctrine/ManagerRegistry.php index 5e958d1865300..bcd16dc06e6f3 100644 --- a/src/Symfony/Bridge/Doctrine/ManagerRegistry.php +++ b/src/Symfony/Bridge/Doctrine/ManagerRegistry.php @@ -14,8 +14,8 @@ use Doctrine\Persistence\AbstractManagerRegistry; use ProxyManager\Proxy\GhostObjectInterface; use ProxyManager\Proxy\LazyLoadingInterface; -use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\VarExporter\LazyObjectInterface; /** * References Doctrine connections and entity/document managers. @@ -29,17 +29,11 @@ abstract class ManagerRegistry extends AbstractManagerRegistry */ protected $container; - /** - * {@inheritdoc} - */ protected function getService($name): object { return $this->container->get($name); } - /** - * {@inheritdoc} - */ protected function resetService($name): void { if (!$this->container->initialized($name)) { @@ -47,8 +41,15 @@ protected function resetService($name): void } $manager = $this->container->get($name); + if ($manager instanceof LazyObjectInterface) { + if (!$manager->resetLazyObject()) { + throw new \LogicException(sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name)); + } + + return; + } if (!$manager instanceof LazyLoadingInterface) { - throw new \LogicException('Resetting a non-lazy manager service is not supported. '.(interface_exists(LazyLoadingInterface::class) && class_exists(RuntimeInstantiator::class) ? sprintf('Declare the "%s" service as lazy.', $name) : 'Try running "composer require symfony/proxy-manager-bridge".')); + throw new \LogicException(sprintf('Resetting a non-lazy manager service is not supported. Declare the "%s" service as lazy.', $name)); } if ($manager instanceof GhostObjectInterface) { throw new \LogicException('Resetting a lazy-ghost-object manager service is not supported.'); diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.php index 14c338e63d691..4e1052a139651 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.php @@ -24,7 +24,7 @@ public function addQuery(string $connectionName, Query $query): void 'sql' => $query->getSql(), 'params' => $query->getParams(), 'types' => $query->getTypes(), - 'executionMS' => [$query, 'getDuration'], // stop() may not be called at this point + 'executionMS' => $query->getDuration(...), // stop() may not be called at this point ]; } diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index 4f9c97186450c..75682577ed2fc 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -38,9 +38,6 @@ public function __construct(EntityManagerInterface $entityManager) $this->entityManager = $entityManager; } - /** - * {@inheritdoc} - */ public function getProperties(string $class, array $context = []): ?array { if (null === $metadata = $this->getMetadata($class)) { @@ -60,9 +57,6 @@ public function getProperties(string $class, array $context = []): ?array return $properties; } - /** - * {@inheritdoc} - */ public function getTypes(string $class, string $property, array $context = []): ?array { if (null === $metadata = $this->getMetadata($class)) { @@ -197,17 +191,11 @@ public function getTypes(string $class, string $property, array $context = []): return null; } - /** - * {@inheritdoc} - */ public function isReadable(string $class, string $property, array $context = []): ?bool { return null; } - /** - * {@inheritdoc} - */ public function isWritable(string $class, string $property, array $context = []): ?bool { if ( diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index 4d8dcb260467d..60f883a0e465f 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -50,9 +50,6 @@ public function __construct(Connection $conn) $this->conn = $conn; } - /** - * {@inheritdoc} - */ public function loadTokenBySeries(string $series): PersistentTokenInterface { // the alias for lastUsed works around case insensitivity in PostgreSQL @@ -69,9 +66,6 @@ public function loadTokenBySeries(string $series): PersistentTokenInterface throw new TokenNotFoundException('No token found.'); } - /** - * {@inheritdoc} - */ public function deleteTokenBySeries(string $series) { $sql = 'DELETE FROM rememberme_token WHERE series=:series'; @@ -84,10 +78,7 @@ public function deleteTokenBySeries(string $series) } } - /** - * {@inheritdoc} - */ - public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed) + public function updateToken(string $series, #[\SensitiveParameter] string $tokenValue, \DateTime $lastUsed) { $sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed WHERE series=:series'; $paramValues = [ @@ -110,9 +101,6 @@ public function updateToken(string $series, string $tokenValue, \DateTime $lastU } } - /** - * {@inheritdoc} - */ public function createNewToken(PersistentTokenInterface $token) { $sql = 'INSERT INTO rememberme_token (class, username, series, value, lastUsed) VALUES (:class, :username, :series, :value, :lastUsed)'; @@ -137,10 +125,7 @@ public function createNewToken(PersistentTokenInterface $token) } } - /** - * {@inheritdoc} - */ - public function verifyToken(PersistentTokenInterface $token, string $tokenValue): bool + public function verifyToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue): bool { // Check if the token value matches the current persisted token if (hash_equals($token->getTokenValue(), $tokenValue)) { @@ -174,10 +159,7 @@ public function verifyToken(PersistentTokenInterface $token, string $tokenValue) return hash_equals($tmpToken->getTokenValue(), $tokenValue); } - /** - * {@inheritdoc} - */ - public function updateExistingToken(PersistentTokenInterface $token, string $tokenValue, \DateTimeInterface $lastUsed): void + public function updateExistingToken(PersistentTokenInterface $token, #[\SensitiveParameter] string $tokenValue, \DateTimeInterface $lastUsed): void { if (!$token instanceof PersistentToken) { return; diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index f7baf6ba69b9d..4c7d14f6894e1 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -69,9 +69,6 @@ public function loadUserByIdentifier(string $identifier): UserInterface return $user; } - /** - * {@inheritdoc} - */ public function refreshUser(UserInterface $user): UserInterface { $class = $this->getClass(); @@ -103,17 +100,12 @@ public function refreshUser(UserInterface $user): UserInterface return $refreshedUser; } - /** - * {@inheritdoc} - */ public function supportsClass(string $class): bool { return $class === $this->getClass() || is_subclass_of($class, $this->getClass()); } /** - * {@inheritdoc} - * * @final */ public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void diff --git a/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php b/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php new file mode 100644 index 0000000000000..629a37256c0f2 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php @@ -0,0 +1,395 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\ArgumentResolver; + +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\Persistence\ObjectManager; +use Doctrine\Persistence\ObjectRepository; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver; +use Symfony\Bridge\Doctrine\Attribute\MapEntity; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\SyntaxError; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +class EntityValueResolverTest extends TestCase +{ + public function testResolveWithoutClass() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $argument = new ArgumentMetadata('arg', null, false, false, null); + + $this->assertSame([], $resolver->resolve($request, $argument)); + } + + public function testResolveWithoutAttribute() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry, null, new MapEntity(disabled: true)); + + $request = new Request(); + $argument = $this->createArgument(); + + $this->assertSame([], $resolver->resolve($request, $argument)); + } + + public function testResolveWithoutManager() + { + $registry = $this->createRegistry(null); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $argument = $this->createArgument(); + + $this->assertSame([], $resolver->resolve($request, $argument)); + } + + public function testResolveWithNoIdAndDataOptional() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $argument = $this->createArgument(null, new MapEntity(), 'arg', true); + + $this->assertSame([], $resolver->resolve($request, $argument)); + } + + public function testResolveWithStripNulls() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $request->attributes->set('arg', null); + $argument = $this->createArgument('stdClass', new MapEntity(stripNull: true), 'arg', true); + + $metadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); + $metadata->expects($this->once()) + ->method('hasField') + ->with('arg') + ->willReturn(true); + + $manager->expects($this->once()) + ->method('getClassMetadata') + ->with('stdClass') + ->willReturn($metadata); + + $manager->expects($this->never()) + ->method('getRepository'); + + $this->assertSame([], $resolver->resolve($request, $argument)); + } + + /** + * @dataProvider idsProvider + */ + public function testResolveWithId(string|int $id) + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $request->attributes->set('id', $id); + + $argument = $this->createArgument('stdClass', new MapEntity(id: 'id')); + + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); + $repository->expects($this->once()) + ->method('find') + ->with($id) + ->willReturn($object = new \stdClass()); + + $manager->expects($this->once()) + ->method('getRepository') + ->with('stdClass') + ->willReturn($repository); + + $this->assertSame([$object], $resolver->resolve($request, $argument)); + } + + public function testResolveWithNullId() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $request->attributes->set('id', null); + + $argument = $this->createArgument(isNullable: true); + + $this->assertSame([null], $resolver->resolve($request, $argument)); + } + + public function testResolveWithConversionFailedException() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $request->attributes->set('id', 'test'); + + $argument = $this->createArgument('stdClass', new MapEntity(id: 'id')); + + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); + $repository->expects($this->once()) + ->method('find') + ->with('test') + ->will($this->throwException(new ConversionException())); + + $manager->expects($this->once()) + ->method('getRepository') + ->with('stdClass') + ->willReturn($repository); + + $this->expectException(NotFoundHttpException::class); + + $resolver->resolve($request, $argument); + } + + public function testUsedProperIdentifier() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $request->attributes->set('id', 1); + $request->attributes->set('entity_id', null); + $request->attributes->set('arg', null); + + $argument = $this->createArgument('stdClass', new MapEntity(id: 'entity_id'), 'arg', true); + + $this->assertSame([null], $resolver->resolve($request, $argument)); + } + + public function idsProvider(): iterable + { + yield [1]; + yield [0]; + yield ['foo']; + } + + public function testResolveGuessOptional() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $request->attributes->set('guess', null); + + $argument = $this->createArgument('stdClass', new MapEntity(), 'arg', true); + + $metadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); + $manager->expects($this->once()) + ->method('getClassMetadata') + ->with('stdClass') + ->willReturn($metadata); + + $manager->expects($this->never())->method('getRepository'); + + $this->assertSame([], $resolver->resolve($request, $argument)); + } + + public function testResolveWithMappingAndExclude() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $request->attributes->set('foo', 1); + $request->attributes->set('bar', 2); + + $argument = $this->createArgument( + 'stdClass', + new MapEntity(mapping: ['foo' => 'Foo'], exclude: ['bar']) + ); + + $metadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); + $metadata->expects($this->once()) + ->method('hasField') + ->with('Foo') + ->willReturn(true); + + $manager->expects($this->once()) + ->method('getClassMetadata') + ->with('stdClass') + ->willReturn($metadata); + + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); + $repository->expects($this->once()) + ->method('findOneBy') + ->with(['Foo' => 1]) + ->willReturn($object = new \stdClass()); + + $manager->expects($this->once()) + ->method('getRepository') + ->with('stdClass') + ->willReturn($repository); + + $this->assertSame([$object], $resolver->resolve($request, $argument)); + } + + public function testExceptionWithExpressionIfNoLanguageAvailable() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); + + $request = new Request(); + $argument = $this->createArgument( + 'stdClass', + new MapEntity(expr: 'repository.find(id)'), + 'arg1' + ); + + $this->expectException(\LogicException::class); + + $resolver->resolve($request, $argument); + } + + public function testExpressionFailureReturns404() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $language = $this->getMockBuilder(ExpressionLanguage::class)->getMock(); + $resolver = new EntityValueResolver($registry, $language); + + $this->expectException(NotFoundHttpException::class); + + $request = new Request(); + $argument = $this->createArgument( + 'stdClass', + new MapEntity(expr: 'repository.someMethod()'), + 'arg1' + ); + + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); + // find should not be attempted on this repository as a fallback + $repository->expects($this->never()) + ->method('find'); + + $manager->expects($this->once()) + ->method('getRepository') + ->willReturn($repository); + + $language->expects($this->once()) + ->method('evaluate') + ->willReturn(null); + + $resolver->resolve($request, $argument); + } + + public function testExpressionMapsToArgument() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $language = $this->getMockBuilder(ExpressionLanguage::class)->getMock(); + $resolver = new EntityValueResolver($registry, $language); + + $request = new Request(); + $request->attributes->set('id', 5); + $argument = $this->createArgument( + 'stdClass', + new MapEntity(expr: 'repository.findOneByCustomMethod(id)'), + 'arg1' + ); + + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); + // find should not be attempted on this repository as a fallback + $repository->expects($this->never()) + ->method('find'); + + $manager->expects($this->once()) + ->method('getRepository') + ->willReturn($repository); + + $language->expects($this->once()) + ->method('evaluate') + ->with('repository.findOneByCustomMethod(id)', [ + 'repository' => $repository, + 'id' => 5, + ]) + ->willReturn($object = new \stdClass()); + + $this->assertSame([$object], $resolver->resolve($request, $argument)); + } + + public function testExpressionSyntaxErrorThrowsException() + { + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $language = $this->getMockBuilder(ExpressionLanguage::class)->getMock(); + $resolver = new EntityValueResolver($registry, $language); + + $request = new Request(); + $argument = $this->createArgument( + 'stdClass', + new MapEntity(expr: 'repository.findOneByCustomMethod(id)'), + 'arg1' + ); + + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); + // find should not be attempted on this repository as a fallback + $repository->expects($this->never()) + ->method('find'); + + $manager->expects($this->once()) + ->method('getRepository') + ->willReturn($repository); + + $language->expects($this->once()) + ->method('evaluate') + ->will($this->throwException(new SyntaxError('syntax error message', 10))); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('syntax error message around position 10'); + $resolver->resolve($request, $argument); + } + + private function createArgument(string $class = null, MapEntity $entity = null, string $name = 'arg', bool $isNullable = false): ArgumentMetadata + { + return new ArgumentMetadata($name, $class ?? \stdClass::class, false, false, null, $isNullable, $entity ? [$entity] : []); + } + + private function createRegistry(ObjectManager $manager = null): ManagerRegistry&MockObject + { + $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); + + $registry->expects($this->any()) + ->method('getManagerForClass') + ->willReturn($manager); + + $registry->expects($this->any()) + ->method('getManager') + ->willReturn($manager); + + return $registry; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php index 1631fa8ae37e7..0ad18f0a66edd 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php @@ -14,10 +14,13 @@ use Doctrine\Common\EventSubscriber; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\ContainerAwareEventManager; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\DependencyInjection\Container; class ContainerAwareEventManagerTest extends TestCase { + use ExpectDeprecationTrait; + private $container; private $evm; @@ -171,15 +174,29 @@ public function testGetListenersForEvent() $this->assertSame([$subscriber1, $listener1, $listener2], array_values($this->evm->getListeners('foo'))); } + /** + * @group legacy + */ public function testGetListeners() { $this->container->set('lazy', $listener1 = new MyListener()); $this->evm->addEventListener('foo', 'lazy'); $this->evm->addEventListener('foo', $listener2 = new MyListener()); + $this->expectDeprecation('Since symfony/doctrine-bridge 6.2: Calling "Symfony\Bridge\Doctrine\ContainerAwareEventManager::getListeners()" without an event name is deprecated. Call "getAllListeners()" instead.'); + $this->assertSame([$listener1, $listener2], array_values($this->evm->getListeners()['foo'])); } + public function testGetAllListeners() + { + $this->container->set('lazy', $listener1 = new MyListener()); + $this->evm->addEventListener('foo', 'lazy'); + $this->evm->addEventListener('foo', $listener2 = new MyListener()); + + $this->assertSame([$listener1, $listener2], array_values($this->evm->getAllListeners()['foo'])); + } + public function testRemoveEventListener() { $this->container->set('lazy', $listener1 = new MyListener()); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Attribute/UuidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Attribute/UuidIdEntity.php index 3d28d4469c1fb..6d16ee75e54ae 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Attribute/UuidIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Attribute/UuidIdEntity.php @@ -19,7 +19,7 @@ class UuidIdEntity { #[Id] - #[Column("uuid")] + #[Column('uuid')] protected $id; public function __construct($id) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php index f03157637b256..c8be89cc760e0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Bridge\Doctrine\Tests\Fixtures; /** diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php index 82811b89ed8c0..73a6419e2f0c0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Bridge\Doctrine\Tests\Fixtures; use Doctrine\ORM\Mapping as ORM; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php index 6655033ab4999..fbad8e05c56f6 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php @@ -20,7 +20,7 @@ class ContainerAwareFixture implements FixtureInterface, ContainerAwareInterface { public $container; - public function setContainer(ContainerInterface $container = null) + public function setContainer(?ContainerInterface $container) { $this->container = $container; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php index a06f4432a761a..8f4c3663d2148 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php @@ -14,7 +14,6 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; -use Symfony\Bridge\Doctrine\Tests\Fixtures\Type\StringWrapper; /** @Entity */ class SingleIntIdStringWrapperNameEntity diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php index 217f90374b276..89edafa67bc39 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php @@ -16,25 +16,16 @@ class StringWrapperType extends StringType { - /** - * {@inheritdoc} - */ public function convertToDatabaseValue($value, AbstractPlatform $platform): mixed { return $value instanceof StringWrapper ? $value->getString() : null; } - /** - * {@inheritdoc} - */ public function convertToPHPValue($value, AbstractPlatform $platform): mixed { return new StringWrapper($value); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'string_wrapper'; diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php index fd5271fc47730..8a63a4e8db248 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; /** * @Entity diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php index 312e6c51b69bf..bebe51dbc711d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php @@ -22,25 +22,16 @@ class DoctrineFooType extends Type { private const NAME = 'foo'; - /** - * {@inheritdoc} - */ public function getName(): string { return self::NAME; } - /** - * {@inheritdoc} - */ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string { return $platform->getClobTypeDeclarationSQL([]); } - /** - * {@inheritdoc} - */ public function convertToDatabaseValue($value, AbstractPlatform $platform): mixed { if (null === $value) { @@ -53,9 +44,6 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): mixe return $foo->bar; } - /** - * {@inheritdoc} - */ public function convertToPHPValue($value, AbstractPlatform $platform): mixed { if (null === $value) { @@ -71,9 +59,6 @@ public function convertToPHPValue($value, AbstractPlatform $platform): mixed return $foo; } - /** - * {@inheritdoc} - */ public function requiresSQLCommentHint(AbstractPlatform $platform): bool { return true; diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php index 61a658096add0..91115abb719ec 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php @@ -14,8 +14,8 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; -use Doctrine\ORM\Mapping\ManyToOne; use Doctrine\ORM\Mapping\JoinColumn; +use Doctrine\ORM\Mapping\ManyToOne; /** * @Entity diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index 20d1e487a23d2..ca4f091b75f9f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -146,7 +146,7 @@ public function testSupportProxy() $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User', 'name'); $user2 = $em->getReference('Symfony\Bridge\Doctrine\Tests\Fixtures\User', ['id1' => 1, 'id2' => 1]); - $this->assertTrue($provider->supportsClass(\get_class($user2))); + $this->assertTrue($provider->supportsClass($user2::class)); } public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided() diff --git a/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php b/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php index 7b739a988a4e2..04021b7fd2ea0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php +++ b/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php @@ -25,9 +25,6 @@ final class TestRepositoryFactory implements RepositoryFactory */ private array $repositoryList = []; - /** - * {@inheritdoc} - */ public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository { $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); diff --git a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php index 48f69eee850a6..6d7aac1487704 100644 --- a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php +++ b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php @@ -23,9 +23,6 @@ abstract class AbstractUidType extends Type */ abstract protected function getUidClass(): string; - /** - * {@inheritdoc} - */ public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { if ($this->hasNativeGuidType($platform)) { @@ -39,8 +36,6 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st } /** - * {@inheritdoc} - * * @throws ConversionException */ public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?AbstractUid @@ -61,8 +56,6 @@ public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Ab } /** - * {@inheritdoc} - * * @throws ConversionException */ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string @@ -88,9 +81,6 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?str } } - /** - * {@inheritdoc} - */ public function requiresSQLCommentHint(AbstractPlatform $platform): bool { return true; diff --git a/src/Symfony/Bridge/Doctrine/Types/UlidType.php b/src/Symfony/Bridge/Doctrine/Types/UlidType.php index 809317b222005..c7c5d6c2a8cab 100644 --- a/src/Symfony/Bridge/Doctrine/Types/UlidType.php +++ b/src/Symfony/Bridge/Doctrine/Types/UlidType.php @@ -15,9 +15,11 @@ final class UlidType extends AbstractUidType { + public const NAME = 'ulid'; + public function getName(): string { - return 'ulid'; + return self::NAME; } protected function getUidClass(): string diff --git a/src/Symfony/Bridge/Doctrine/Types/UuidType.php b/src/Symfony/Bridge/Doctrine/Types/UuidType.php index bbf0394034a06..a7a0c2ba83c0f 100644 --- a/src/Symfony/Bridge/Doctrine/Types/UuidType.php +++ b/src/Symfony/Bridge/Doctrine/Types/UuidType.php @@ -15,9 +15,11 @@ final class UuidType extends AbstractUidType { + public const NAME = 'uuid'; + public function getName(): string { - return 'uuid'; + return self::NAME; } protected function getUidClass(): string diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php index 7d65c898ccb47..5e9048808023d 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php @@ -45,8 +45,6 @@ class UniqueEntity extends Constraint protected static $errorNames = self::ERROR_NAMES; /** - * {@inheritdoc} - * * @param array|string $fields the combination of fields that must contain unique values or a set of options */ public function __construct( @@ -92,9 +90,6 @@ public function validatedBy(): string return $this->service; } - /** - * {@inheritdoc} - */ public function getTargets(): string|array { return self::CLASS_CONSTRAINT; diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 1274607287560..71d73d88a4a44 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -75,14 +75,14 @@ public function validate(mixed $entity, Constraint $constraint) throw new ConstraintDefinitionException(sprintf('Object manager "%s" does not exist.', $constraint->em)); } } else { - $em = $this->registry->getManagerForClass(\get_class($entity)); + $em = $this->registry->getManagerForClass($entity::class); if (!$em) { throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', get_debug_type($entity))); } } - $class = $em->getClassMetadata(\get_class($entity)); + $class = $em->getClassMetadata($entity::class); $criteria = []; $hasNullValue = false; @@ -136,7 +136,7 @@ public function validate(mixed $entity, Constraint $constraint) throw new ConstraintDefinitionException(sprintf('The "%s" entity repository does not support the "%s" entity. The entity should be an instance of or extend "%s".', $constraint->entityClass, $class->getName(), $supportedClass)); } } else { - $repository = $em->getRepository(\get_class($entity)); + $repository = $em->getRepository($entity::class); } $arguments = [$criteria]; @@ -203,7 +203,7 @@ private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, return (string) $value; } - if ($class->getName() !== $idClass = \get_class($value)) { + if ($class->getName() !== $idClass = $value::class) { // non unique value might be a composite PK that consists of other entity objects if ($em->getMetadataFactory()->hasMetadataFor($idClass)) { $identifiers = $em->getClassMetadata($idClass)->getIdentifierValues($value); @@ -224,7 +224,7 @@ private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, if (!\is_object($id) || $id instanceof \DateTimeInterface) { $idAsString = $this->formatValue($id, self::PRETTY_DATE); } else { - $idAsString = sprintf('object("%s")', \get_class($id)); + $idAsString = sprintf('object("%s")', $id::class); } $id = sprintf('%s => %s', $field, $idAsString); diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php index 3e7922ec79276..c50c6e98120ab 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php @@ -30,6 +30,6 @@ public function __construct(ManagerRegistry $registry) public function initialize(object $object) { - $this->registry->getManagerForClass(\get_class($object))?->initializeObject($object); + $this->registry->getManagerForClass($object::class)?->initializeObject($object); } } diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php index 45758d74b8685..d0e976c2c8853 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php @@ -42,9 +42,6 @@ public function __construct(EntityManagerInterface $entityManager, string $class $this->classValidatorRegexp = $classValidatorRegexp; } - /** - * {@inheritdoc} - */ public function loadClassMetadata(ClassMetadata $metadata): bool { $className = $metadata->getClassName(); diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 8de4371499e8d..54dd86a53dfd3 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "doctrine/event-manager": "~1.0", + "doctrine/event-manager": "^1.2|^2", "doctrine/persistence": "^2|^3", "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-ctype": "~1.8", @@ -30,7 +30,7 @@ "symfony/config": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/form": "^5.4.9|^6.0.9", - "symfony/http-kernel": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", "symfony/messenger": "^5.4|^6.0", "symfony/doctrine-messenger": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", @@ -57,7 +57,7 @@ "symfony/cache": "<5.4", "symfony/dependency-injection": "<5.4", "symfony/form": "<5.4", - "symfony/http-kernel": "<5.4", + "symfony/http-kernel": "<6.2", "symfony/messenger": "<5.4", "symfony/property-info": "<5.4", "symfony/security-bundle": "<5.4", diff --git a/src/Symfony/Bridge/Monolog/Formatter/CompatibilityFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/CompatibilityFormatter.php index 08cd70983b3ba..aa374445b08c2 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/CompatibilityFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/CompatibilityFormatter.php @@ -26,9 +26,6 @@ trait CompatibilityFormatter { abstract private function doFormat(array|LogRecord $record): mixed; - /** - * {@inheritdoc} - */ public function format(LogRecord $record): mixed { return $this->doFormat($record); @@ -46,9 +43,6 @@ trait CompatibilityFormatter { abstract private function doFormat(array|LogRecord $record): mixed; - /** - * {@inheritdoc} - */ public function format(array $record): mixed { return $this->doFormat($record); diff --git a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php index b8ed640e9c4aa..31f2197b75afb 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php @@ -91,9 +91,6 @@ public function __construct(array $options = []) } } - /** - * {@inheritdoc} - */ public function formatBatch(array $records): mixed { foreach ($records as $key => $record) { diff --git a/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php index 92cf6c3e887b4..14b7da442b605 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php @@ -43,9 +43,6 @@ private function doFormat(array|LogRecord $record): mixed return $record; } - /** - * {@inheritdoc} - */ public function formatBatch(array $records): mixed { foreach ($records as $k => $record) { diff --git a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php index 1fa698a9390b6..c84db4d897df8 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php @@ -50,9 +50,6 @@ public function onKernelResponse(ResponseEvent $event) $this->headers = []; } - /** - * {@inheritdoc} - */ protected function sendHeader($header, $content): void { if (!self::$sendHeaders) { diff --git a/src/Symfony/Bridge/Monolog/Handler/CompatibilityHandler.php b/src/Symfony/Bridge/Monolog/Handler/CompatibilityHandler.php index dbeb59e4feb3b..051698f06515c 100644 --- a/src/Symfony/Bridge/Monolog/Handler/CompatibilityHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/CompatibilityHandler.php @@ -26,9 +26,6 @@ trait CompatibilityHandler { abstract private function doHandle(array|LogRecord $record): bool; - /** - * {@inheritdoc} - */ public function handle(LogRecord $record): bool { return $this->doHandle($record); @@ -46,9 +43,6 @@ trait CompatibilityHandler { abstract private function doHandle(array|LogRecord $record): bool; - /** - * {@inheritdoc} - */ public function handle(array $record): bool { return $this->doHandle($record); diff --git a/src/Symfony/Bridge/Monolog/Handler/CompatibilityProcessingHandler.php b/src/Symfony/Bridge/Monolog/Handler/CompatibilityProcessingHandler.php index c84c457859d52..e15a00286da83 100644 --- a/src/Symfony/Bridge/Monolog/Handler/CompatibilityProcessingHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/CompatibilityProcessingHandler.php @@ -26,9 +26,6 @@ trait CompatibilityProcessingHandler { abstract private function doWrite(array|LogRecord $record): void; - /** - * {@inheritdoc} - */ protected function write(LogRecord $record): void { $this->doWrite($record); @@ -46,9 +43,6 @@ trait CompatibilityProcessingHandler { abstract private function doWrite(array|LogRecord $record): void; - /** - * {@inheritdoc} - */ protected function write(array $record): void { $this->doWrite($record); diff --git a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php index 88936ff2bfbd8..c3426bb51cb0f 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php @@ -37,9 +37,6 @@ trait CompatibilityIsHandlingHandler { abstract private function doIsHandling(array|LogRecord $record): bool; - /** - * {@inheritdoc} - */ public function isHandling(LogRecord $record): bool { return $this->doIsHandling($record); @@ -57,9 +54,6 @@ trait CompatibilityIsHandlingHandler { abstract private function doIsHandling(array|LogRecord $record): bool; - /** - * {@inheritdoc} - */ public function isHandling(array $record): bool { return $this->doIsHandling($record); @@ -121,17 +115,11 @@ public function __construct(OutputInterface $output = null, bool $bubble = true, $this->consoleFormatterOptions = $consoleFormatterOptions; } - /** - * {@inheritdoc} - */ private function doIsHandling(array|LogRecord $record): bool { return $this->updateLevel() && parent::isHandling($record); } - /** - * {@inheritdoc} - */ private function doHandle(array|LogRecord $record): bool { // we have to update the logging level each time because the verbosity of the @@ -179,9 +167,6 @@ public function onTerminate(ConsoleTerminateEvent $event) $this->close(); } - /** - * {@inheritdoc} - */ public static function getSubscribedEvents(): array { return [ @@ -196,9 +181,6 @@ private function doWrite(array|LogRecord $record): void $this->output->write((string) $record['formatted'], false, $this->output->getVerbosity()); } - /** - * {@inheritdoc} - */ protected function getDefaultFormatter(): FormatterInterface { if (!class_exists(CliDumper::class)) { diff --git a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php index d60b02a287558..3627f50551493 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php @@ -52,9 +52,6 @@ public function onKernelResponse(ResponseEvent $event) $this->headers = []; } - /** - * {@inheritdoc} - */ protected function sendHeader($header, $content): void { if (!self::$sendHeaders) { diff --git a/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php index b75accae76a84..e0862f8f45df3 100644 --- a/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php @@ -41,9 +41,6 @@ public function __construct(MailerInterface $mailer, callable|Email $messageTemp $this->messageTemplate = $messageTemplate instanceof Email ? $messageTemplate : $messageTemplate(...); } - /** - * {@inheritdoc} - */ public function handleBatch(array $records): void { $messages = []; @@ -65,14 +62,11 @@ public function handleBatch(array $records): void } } - if (!empty($messages)) { + if ($messages) { $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); } } - /** - * {@inheritdoc} - */ private function doWrite(array|LogRecord $record): void { $this->send((string) $record['formatted'], [$record]); diff --git a/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php b/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php index 3b2319fb5812a..4ba90f7efbea3 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php @@ -29,9 +29,6 @@ class ServerLogHandler extends AbstractProcessingHandler use CompatibilityProcessingHandler; use ServerLogHandlerTrait; - /** - * {@inheritdoc} - */ protected function getDefaultFormatter(): FormatterInterface { return new VarDumperFormatter(); @@ -47,9 +44,6 @@ class ServerLogHandler extends AbstractProcessingHandler use CompatibilityProcessingHandler; use ServerLogHandlerTrait; - /** - * {@inheritdoc} - */ protected function getDefaultFormatter() { return new VarDumperFormatter(); @@ -127,9 +121,6 @@ private function doWrite(array|LogRecord $record): void } } - /** - * {@inheritdoc} - */ protected function getDefaultFormatter(): FormatterInterface { return new VarDumperFormatter(); diff --git a/src/Symfony/Bridge/Monolog/Logger.php b/src/Symfony/Bridge/Monolog/Logger.php index 2b5f312053048..4e2168a114866 100644 --- a/src/Symfony/Bridge/Monolog/Logger.php +++ b/src/Symfony/Bridge/Monolog/Logger.php @@ -22,9 +22,6 @@ */ class Logger extends BaseLogger implements DebugLoggerInterface, ResetInterface { - /** - * {@inheritdoc} - */ public function getLogs(Request $request = null): array { if ($logger = $this->getDebugLogger()) { @@ -34,9 +31,6 @@ public function getLogs(Request $request = null): array return []; } - /** - * {@inheritdoc} - */ public function countErrors(Request $request = null): int { if ($logger = $this->getDebugLogger()) { @@ -46,9 +40,6 @@ public function countErrors(Request $request = null): int return 0; } - /** - * {@inheritdoc} - */ public function clear() { if ($logger = $this->getDebugLogger()) { @@ -56,9 +47,6 @@ public function clear() } } - /** - * {@inheritdoc} - */ public function reset(): void { $this->clear(); diff --git a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php index a033d73c3b187..5c135d10065b0 100644 --- a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php @@ -33,7 +33,7 @@ public function __construct(RequestStack $requestStack = null) private function doInvoke(array|LogRecord $record): array|LogRecord { - $hash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : ''; + $key = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_id($request) : ''; $timestamp = $timestampRfc3339 = false; if ($record['datetime'] instanceof \DateTimeInterface) { @@ -43,7 +43,7 @@ private function doInvoke(array|LogRecord $record): array|LogRecord $timestampRfc3339 = (new \DateTimeImmutable($record['datetime']))->format(\DateTimeInterface::RFC3339_EXTENDED); } - $this->records[$hash][] = [ + $this->records[$key][] = [ 'timestamp' => $timestamp, 'timestamp_rfc3339' => $timestampRfc3339, 'message' => $record['message'], @@ -53,8 +53,8 @@ private function doInvoke(array|LogRecord $record): array|LogRecord 'channel' => $record['channel'] ?? '', ]; - if (!isset($this->errorCount[$hash])) { - $this->errorCount[$hash] = 0; + if (!isset($this->errorCount[$key])) { + $this->errorCount[$key] = 0; } switch ($record['level']) { @@ -62,19 +62,16 @@ private function doInvoke(array|LogRecord $record): array|LogRecord case Logger::CRITICAL: case Logger::ALERT: case Logger::EMERGENCY: - ++$this->errorCount[$hash]; + ++$this->errorCount[$key]; } return $record; } - /** - * {@inheritdoc} - */ public function getLogs(Request $request = null): array { if (null !== $request) { - return $this->records[spl_object_hash($request)] ?? []; + return $this->records[spl_object_id($request)] ?? []; } if (0 === \count($this->records)) { @@ -84,30 +81,21 @@ public function getLogs(Request $request = null): array return array_merge(...array_values($this->records)); } - /** - * {@inheritdoc} - */ public function countErrors(Request $request = null): int { if (null !== $request) { - return $this->errorCount[spl_object_hash($request)] ?? 0; + return $this->errorCount[spl_object_id($request)] ?? 0; } return array_sum($this->errorCount); } - /** - * {@inheritdoc} - */ public function clear() { $this->records = []; $this->errorCount = []; } - /** - * {@inheritdoc} - */ public function reset() { $this->clear(); diff --git a/src/Symfony/Bridge/Monolog/Processor/SwitchUserTokenProcessor.php b/src/Symfony/Bridge/Monolog/Processor/SwitchUserTokenProcessor.php index bb3f6ff73d0cd..22d86f0b3edb5 100644 --- a/src/Symfony/Bridge/Monolog/Processor/SwitchUserTokenProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/SwitchUserTokenProcessor.php @@ -23,17 +23,11 @@ */ class SwitchUserTokenProcessor extends AbstractTokenProcessor { - /** - * {@inheritdoc} - */ protected function getKey(): string { return 'impersonator_token'; } - /** - * {@inheritdoc} - */ protected function getToken(): ?TokenInterface { $token = $this->tokenStorage->getToken(); diff --git a/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php b/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php index c824ea1761efd..0e0085718e439 100644 --- a/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php @@ -23,17 +23,11 @@ */ class TokenProcessor extends AbstractTokenProcessor { - /** - * {@inheritdoc} - */ protected function getKey(): string { return 'token'; } - /** - * {@inheritdoc} - */ protected function getToken(): ?TokenInterface { return $this->tokenStorage->getToken(); diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index 9b8a974ecc5e6..e7e3e29862bd4 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.2 +--- + + * Add support for mocking the `hrtime()` function + 6.1 --- diff --git a/src/Symfony/Bridge/PhpUnit/ClockMock.php b/src/Symfony/Bridge/PhpUnit/ClockMock.php index 7280d44dc16f8..4f826f1c84b44 100644 --- a/src/Symfony/Bridge/PhpUnit/ClockMock.php +++ b/src/Symfony/Bridge/PhpUnit/ClockMock.php @@ -90,6 +90,19 @@ public static function gmdate($format, $timestamp = null) return \gmdate($format, $timestamp); } + public static function hrtime($asNumber = false) + { + $ns = (self::$now - (int) self::$now) * 1000000000; + + if ($asNumber) { + $number = sprintf('%d%d', (int) self::$now, $ns); + + return PHP_INT_SIZE === 8 ? (int) $number : (float) $number; + } + + return [(int) self::$now, (int) $ns]; + } + public static function register($class) { $self = static::class; @@ -137,6 +150,11 @@ function gmdate(\$format, \$timestamp = null) { return \\$self::gmdate(\$format, \$timestamp); } + +function hrtime(\$asNumber = false) +{ + return \\$self::hrtime(\$asNumber); +} EOPHP ); } diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index 5eda2bafdfb10..003680a5c074b 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -16,7 +16,6 @@ use PHPUnit\Metadata\Api\Groups; use PHPUnit\Util\Test; use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor; -use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; use Symfony\Component\ErrorHandler\DebugClassLoader; class_exists(Groups::class); @@ -81,7 +80,7 @@ public function __construct($message, array $trace, $file) } if ('trigger_error' === $trace[$j]['function'] && !isset($trace[$j]['class'])) { - if (\in_array($trace[1 + $j]['class'], [DebugClassLoader::class, LegacyDebugClassLoader::class], true)) { + if (DebugClassLoader::class === $trace[1 + $j]['class']) { $class = $trace[1 + $j]['args'][0]; $this->triggeringFile = isset($trace[1 + $j]['args'][1]) ? realpath($trace[1 + $j]['args'][1]) : (new \ReflectionClass($class))->getFileName(); $this->getOriginalFilesStack(); @@ -323,9 +322,6 @@ private static function getVendors() if (class_exists(DebugClassLoader::class, false)) { self::$vendors[] = \dirname((new \ReflectionClass(DebugClassLoader::class))->getFileName()); } - if (class_exists(LegacyDebugClassLoader::class, false)) { - self::$vendors[] = \dirname((new \ReflectionClass(LegacyDebugClassLoader::class))->getFileName()); - } foreach (get_declared_classes() as $class) { if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { $r = new \ReflectionClass($class); diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV7.php index fcf5c4505d3da..99a1e683525dd 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV7.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV7.php @@ -17,15 +17,10 @@ use Symfony\Bridge\PhpUnit\SymfonyTestsListener; /** - * {@inheritdoc} - * * @internal */ class CommandForV7 extends BaseCommand { - /** - * {@inheritdoc} - */ protected function createRunner(): BaseRunner { $this->arguments['listeners'] ?? $this->arguments['listeners'] = []; diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php index 351f02f2230ec..6dd2767361db3 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php @@ -20,15 +20,10 @@ use Symfony\Bridge\PhpUnit\SymfonyTestsListener; /** - * {@inheritdoc} - * * @internal */ class CommandForV9 extends BaseCommand { - /** - * {@inheritdoc} - */ protected function createRunner(): BaseRunner { $this->arguments['listeners'] ?? $this->arguments['listeners'] = []; diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index 0fc2f2d623358..29dd75e478b5b 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -23,7 +23,6 @@ use Symfony\Bridge\PhpUnit\ClockMock; use Symfony\Bridge\PhpUnit\DnsMock; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; use Symfony\Component\ErrorHandler\DebugClassLoader; /** @@ -61,7 +60,7 @@ public function __construct(array $mockedNamespaces = []) Blacklist::$blacklistedClassNames[__CLASS__] = 2; } - $enableDebugClassLoader = class_exists(DebugClassLoader::class) || class_exists(LegacyDebugClassLoader::class); + $enableDebugClassLoader = class_exists(DebugClassLoader::class); foreach ($mockedNamespaces as $type => $namespaces) { if (!\is_array($namespaces)) { @@ -82,11 +81,7 @@ public function __construct(array $mockedNamespaces = []) } } if ($enableDebugClassLoader) { - if (class_exists(DebugClassLoader::class)) { - DebugClassLoader::enable(); - } else { - LegacyDebugClassLoader::enable(); - } + DebugClassLoader::enable(); } if (self::$globallyEnabled) { $this->state = -2; diff --git a/src/Symfony/Bridge/PhpUnit/Tests/ClockMockTest.php b/src/Symfony/Bridge/PhpUnit/Tests/ClockMockTest.php index 5af0617ba5a7f..7df7865d1c9be 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/ClockMockTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/ClockMockTest.php @@ -69,4 +69,14 @@ public function testGmDate() $this->assertSame('1555075769', gmdate('U')); } + + public function testHrTime() + { + $this->assertSame([1234567890, 125000000], hrtime()); + } + + public function testHrTimeAsNumber() + { + $this->assertSame(1234567890125000000, hrtime(true)); + } } diff --git a/src/Symfony/Bridge/ProxyManager/Internal/LazyLoadingFactoryTrait.php b/src/Symfony/Bridge/ProxyManager/Internal/LazyLoadingFactoryTrait.php new file mode 100644 index 0000000000000..cabff29b3c5ec --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Internal/LazyLoadingFactoryTrait.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\Internal; + +use ProxyManager\Configuration; + +/** + * @internal + */ +trait LazyLoadingFactoryTrait +{ + private readonly ProxyGenerator $generator; + + public function __construct(Configuration $config, ProxyGenerator $generator) + { + parent::__construct($config); + $this->generator = $generator; + } + + public function getGenerator(): ProxyGenerator + { + return $this->generator; + } +} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php b/src/Symfony/Bridge/ProxyManager/Internal/ProxyGenerator.php similarity index 84% rename from src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php rename to src/Symfony/Bridge/ProxyManager/Internal/ProxyGenerator.php index d665070f02454..26c95448eb2bb 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php +++ b/src/Symfony/Bridge/ProxyManager/Internal/ProxyGenerator.php @@ -9,23 +9,21 @@ * file that was distributed with this source code. */ -namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper; +namespace Symfony\Bridge\ProxyManager\Internal; use Laminas\Code\Generator\ClassGenerator; -use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator as BaseGenerator; +use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator; +use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; use Symfony\Component\DependencyInjection\Definition; /** * @internal */ -class LazyLoadingValueHolderGenerator extends BaseGenerator +class ProxyGenerator implements ProxyGeneratorInterface { - /** - * {@inheritdoc} - */ public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator, array $proxyOptions = []): void { - parent::generate($originalClass, $classGenerator, $proxyOptions); + (new LazyLoadingValueHolderGenerator())->generate($originalClass, $classGenerator, $proxyOptions); foreach ($classGenerator->getMethods() as $method) { if (str_starts_with($originalClass->getFilename(), __FILE__)) { @@ -43,7 +41,11 @@ public function generate(\ReflectionClass $originalClass, ClassGenerator $classG public function getProxifiedClass(Definition $definition): ?string { if (!$definition->hasTag('proxy')) { - return ($class = $definition->getClass()) && (class_exists($class) || interface_exists($class, false)) ? $class : null; + if (!($class = $definition->getClass()) || !(class_exists($class) || interface_exists($class, false))) { + return null; + } + + return (new \ReflectionClass($class))->name; } if (!$definition->isLazy()) { throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": setting the "proxy" tag on a service requires it to be "lazy".', $definition->getClass())); diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php deleted file mode 100644 index 696c0b2e3952e..0000000000000 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator; - -use ProxyManager\Factory\LazyLoadingValueHolderFactory as BaseFactory; -use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; -use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenerator; - -/** - * @internal - */ -class LazyLoadingValueHolderFactory extends BaseFactory -{ - private ProxyGeneratorInterface $generator; - - /** - * {@inheritdoc} - */ - public function getGenerator(): ProxyGeneratorInterface - { - return $this->generator ??= new LazyLoadingValueHolderGenerator(); - } -} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php index 003986b8d00fc..59fcdc022efce 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -12,8 +12,11 @@ namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator; use ProxyManager\Configuration; +use ProxyManager\Factory\LazyLoadingValueHolderFactory; use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; use ProxyManager\Proxy\LazyLoadingInterface; +use Symfony\Bridge\ProxyManager\Internal\LazyLoadingFactoryTrait; +use Symfony\Bridge\ProxyManager\Internal\ProxyGenerator; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; @@ -25,34 +28,34 @@ */ class RuntimeInstantiator implements InstantiatorInterface { - private LazyLoadingValueHolderFactory $factory; + private Configuration $config; + private ProxyGenerator $generator; public function __construct() { - $config = new Configuration(); - $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); - - $this->factory = new LazyLoadingValueHolderFactory($config); + $this->config = new Configuration(); + $this->config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); + $this->generator = new ProxyGenerator(); } - /** - * {@inheritdoc} - */ public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator): object { - return $this->factory->createProxy( - $this->factory->getGenerator()->getProxifiedClass($definition), - function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) { - $wrappedInstance = $realInstantiator(); - - $proxy->setProxyInitializer(null); - - return true; - }, - [ - 'fluentSafe' => $definition->hasTag('proxy'), - 'skipDestructor' => true, - ] - ); + $proxifiedClass = new \ReflectionClass($this->generator->getProxifiedClass($definition)); + + $factory = new class($this->config, $this->generator) extends LazyLoadingValueHolderFactory { + use LazyLoadingFactoryTrait; + }; + + $initializer = static function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) { + $wrappedInstance = $realInstantiator(); + $proxy->setProxyInitializer(null); + + return true; + }; + + return $factory->createProxy($proxifiedClass->name, $initializer, [ + 'fluentSafe' => $definition->hasTag('proxy'), + 'skipDestructor' => true, + ]); } } diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index a5d3707e8ae61..82ff95dc4cc93 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -13,6 +13,7 @@ use Laminas\Code\Generator\ClassGenerator; use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy; +use Symfony\Bridge\ProxyManager\Internal\ProxyGenerator; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; @@ -26,27 +27,23 @@ class ProxyDumper implements DumperInterface { private string $salt; - private LazyLoadingValueHolderGenerator $proxyGenerator; + private ProxyGenerator $proxyGenerator; private BaseGeneratorStrategy $classGenerator; public function __construct(string $salt = '') { $this->salt = $salt; - $this->proxyGenerator = new LazyLoadingValueHolderGenerator(); + $this->proxyGenerator = new ProxyGenerator(); $this->classGenerator = new BaseGeneratorStrategy(); } - /** - * {@inheritdoc} - */ - public function isProxyCandidate(Definition $definition): bool + public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null, string $id = null): bool { + $asGhostObject = false; + return ($definition->isLazy() || $definition->hasTag('proxy')) && $this->proxyGenerator->getProxifiedClass($definition); } - /** - * {@inheritdoc} - */ public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string { $instantiation = 'return'; @@ -55,10 +52,11 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ $instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); } - $proxyClass = $this->getProxyClassName($definition); + $proxifiedClass = new \ReflectionClass($this->proxyGenerator->getProxifiedClass($definition)); + $proxyClass = $this->getProxyClassName($proxifiedClass->name); return <<createProxy('$proxyClass', function () { return \\$proxyClass::staticProxyConstructor(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) { \$wrappedInstance = $factoryCode; @@ -74,10 +72,7 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ EOF; } - /** - * {@inheritdoc} - */ - public function getProxyCode(Definition $definition): string + public function getProxyCode(Definition $definition, string $id = null): string { $code = $this->classGenerator->generate($this->generateProxyClass($definition)); $code = preg_replace('/^(class [^ ]++ extends )([^\\\\])/', '$1\\\\$2', $code); @@ -85,20 +80,15 @@ public function getProxyCode(Definition $definition): string return $code; } - /** - * Produces the proxy class name for the given definition. - */ - private function getProxyClassName(Definition $definition): string + private function getProxyClassName(string $class): string { - $class = $this->proxyGenerator->getProxifiedClass($definition); - - return preg_replace('/^.*\\\\/', '', $class).'_'.$this->getIdentifierSuffix($definition); + return preg_replace('/^.*\\\\/', '', $class).'_'.substr(hash('sha256', $class.$this->salt), -7); } private function generateProxyClass(Definition $definition): ClassGenerator { - $generatedClass = new ClassGenerator($this->getProxyClassName($definition)); $class = $this->proxyGenerator->getProxifiedClass($definition); + $generatedClass = new ClassGenerator($this->getProxyClassName($class)); $this->proxyGenerator->generate(new \ReflectionClass($class), $generatedClass, [ 'fluentSafe' => $definition->hasTag('proxy'), @@ -107,11 +97,4 @@ private function generateProxyClass(Definition $definition): ClassGenerator return $generatedClass; } - - private function getIdentifierSuffix(Definition $definition): string - { - $class = $this->proxyGenerator->getProxifiedClass($definition); - - return substr(hash('sha256', $class.$this->salt), -7); - } } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php index 69b7239655944..0967f1b1d5b23 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php @@ -30,11 +30,10 @@ class ContainerBuilderTest extends TestCase public function testCreateProxyServiceWithRuntimeInstantiator() { $builder = new ContainerBuilder(); - $builder->setProxyInstantiator(new RuntimeInstantiator()); $builder->register('foo1', ProxyManagerBridgeFooClass::class)->setFile(__DIR__.'/Fixtures/includes/foo.php')->setPublic(true); - $builder->getDefinition('foo1')->setLazy(true); + $builder->getDefinition('foo1')->setLazy(true)->addTag('proxy', ['interface' => ProxyManagerBridgeFooClass::class]); $builder->compile(); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php index 8bc017bb8df71..aedfff33c56c5 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php @@ -62,12 +62,11 @@ private function dumpLazyServiceProjectServiceContainer() { $container = new ContainerBuilder(); - $container->register('foo', 'stdClass')->setPublic(true); - $container->getDefinition('foo')->setLazy(true); + $container->register('foo', \stdClass::class)->setPublic(true); + $container->getDefinition('foo')->setLazy(true)->addTag('proxy', ['interface' => \stdClass::class]); $container->compile(); $dumper = new PhpDumper($container); - $dumper->setProxyDumper(new ProxyDumper()); return $dumper->dump(['class' => 'LazyServiceProjectServiceContainer']); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt index 906fff68f7bbe..825c1051ca38f 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt @@ -5,7 +5,7 @@ class LazyServiceProjectServiceContainer extends Container {%a protected function getFooService($lazyLoad = true) { - if ($lazyLoad) { + if (true === $lazyLoad) { return $this->services['foo'] = $this->createProxy('stdClass_%s', function () { return %S\stdClass_%s(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) { $wrappedInstance = $this->getFooService(false); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php index e202fad702655..fc04526ced826 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php @@ -30,9 +30,6 @@ class RuntimeInstantiatorTest extends TestCase */ protected $instantiator; - /** - * {@inheritdoc} - */ protected function setUp(): void { $this->instantiator = new RuntimeInstantiator(); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php index af3b972cdfdcf..c06cb534b6e79 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php @@ -7,7 +7,7 @@ public function getFooService($lazyLoad = true) { - if ($lazyLoad) { + if (true === $lazyLoad) { return $this->privates['foo'] = $this->createProxy('SunnyInterface_1eff735', function () { return \SunnyInterface_1eff735::staticProxyConstructor(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) { $wrappedInstance = $this->getFooService(false); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index e787da909bbb3..2d7428fac8d4d 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -28,9 +28,6 @@ class ProxyDumperTest extends TestCase */ protected $dumper; - /** - * {@inheritdoc} - */ protected function setUp(): void { $this->dumper = new ProxyDumper(); diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index d141f99ba6ee5..1e4333d8ad96d 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -18,10 +18,10 @@ "require": { "php": ">=8.1", "friendsofphp/proxy-manager-lts": "^1.0.2", - "symfony/dependency-injection": "^5.4|^6.0" + "symfony/dependency-injection": "^6.2" }, "require-dev": { - "symfony/config": "^5.4|^6.0" + "symfony/config": "^6.1" }, "autoload": { "psr-4": { "Symfony\\Bridge\\ProxyManager\\": "" }, diff --git a/src/Symfony/Bridge/Twig/AppVariable.php b/src/Symfony/Bridge/Twig/AppVariable.php index 43d6e3ffc9056..d342f9fbaf03f 100644 --- a/src/Symfony/Bridge/Twig/AppVariable.php +++ b/src/Symfony/Bridge/Twig/AppVariable.php @@ -158,4 +158,25 @@ public function getFlashes(string|array $types = null): array return $result; } + + public function getCurrent_Route(): ?string + { + if (!isset($this->requestStack)) { + throw new \RuntimeException('The "app.current_route" variable is not available.'); + } + + return $this->getRequest()?->attributes->get('_route'); + } + + /** + * @return array + */ + public function getCurrent_Route_Parameters(): array + { + if (!isset($this->requestStack)) { + throw new \RuntimeException('The "app.current_route_parameters" variable is not available.'); + } + + return $this->getRequest()?->attributes->get('_route_params') ?? []; + } } diff --git a/src/Symfony/Bridge/Twig/Attribute/Template.php b/src/Symfony/Bridge/Twig/Attribute/Template.php new file mode 100644 index 0000000000000..f094f42a4a6e2 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Attribute/Template.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Attribute; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)] +class Template +{ + public function __construct( + /** + * The name of the template to render. + */ + public string $template, + + /** + * The controller method arguments to pass to the template. + */ + public ?array $vars = null, + + /** + * Enables streaming the template. + */ + public bool $stream = false, + ) { + } +} diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 819251bf0d874..8edc9b80190b4 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +6.2 +--- + + * Add `form_label_content` and `form_help_content` block to form themes + * Add `#[Template()]` to describe how to render arrays returned by controllers + * Add support for toggle buttons in Bootstrap 5 form theme + * Add `app.current_route` and `app.current_route_parameters` variables + 6.1 --- diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index d5e64859a6c20..6fae02cb9bc91 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -101,16 +101,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new InvalidArgumentException(sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class)); } - switch ($input->getOption('format')) { - case 'text': - $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter); - break; - case 'json': - $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter); - break; - default: - throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); - } + match ($input->getOption('format')) { + 'text' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), + 'json' => $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter), + default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))), + }; return 0; } @@ -384,7 +379,7 @@ private function getPrettyMetadata(string $type, mixed $entity, bool $decorated) if ('globals' === $type) { if (\is_object($meta)) { - return ' = object('.\get_class($meta).')'; + return ' = object('.$meta::class.')'; } $description = substr(@json_encode($meta), 0, 50); diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php index a26a620cfddb0..a8da618bc0738 100644 --- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php +++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php @@ -38,16 +38,10 @@ public function __construct(Profile $profile, Environment $twig = null) $this->twig = $twig; } - /** - * {@inheritdoc} - */ public function collect(Request $request, Response $response, \Throwable $exception = null) { } - /** - * {@inheritdoc} - */ public function reset() { $this->profile->reset(); @@ -55,9 +49,6 @@ public function reset() $this->data = []; } - /** - * {@inheritdoc} - */ public function lateCollect() { $this->data['profile'] = serialize($this->profile); @@ -187,9 +178,6 @@ private function computeData(Profile $profile) return $data; } - /** - * {@inheritdoc} - */ public function getName(): string { return 'twig'; diff --git a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php index 4dde73f62c902..9086c22d5c3cb 100644 --- a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php +++ b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php @@ -39,22 +39,19 @@ public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorR $this->debug = \is_bool($debug) ? $debug : $debug(...); } - /** - * {@inheritdoc} - */ public function render(\Throwable $exception): FlattenException { - $exception = $this->fallbackErrorRenderer->render($exception); - $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception); + $flattenException = FlattenException::createFromThrowable($exception); + $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($flattenException); - if ($debug || !$template = $this->findTemplate($exception->getStatusCode())) { - return $exception; + if ($debug || !$template = $this->findTemplate($flattenException->getStatusCode())) { + return $this->fallbackErrorRenderer->render($exception); } - return $exception->setAsString($this->twig->render($template, [ - 'exception' => $exception, - 'status_code' => $exception->getStatusCode(), - 'status_text' => $exception->getStatusText(), + return $flattenException->setAsString($this->twig->render($template, [ + 'exception' => $flattenException, + 'status_code' => $flattenException->getStatusCode(), + 'status_text' => $flattenException->getStatusText(), ])); } diff --git a/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php b/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php new file mode 100644 index 0000000000000..96924442d1e48 --- /dev/null +++ b/src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\EventListener; + +use Symfony\Bridge\Twig\Attribute\Template; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ViewEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Twig\Environment; + +class TemplateAttributeListener implements EventSubscriberInterface +{ + public function __construct( + private Environment $twig, + ) { + } + + public function onKernelView(ViewEvent $event) + { + $parameters = $event->getControllerResult(); + + if (!\is_array($parameters ?? [])) { + return; + } + $attribute = $event->getRequest()->attributes->get('_template'); + + if (!$attribute instanceof Template && !$attribute = $event->controllerArgumentsEvent?->getAttributes()[Template::class][0] ?? null) { + return; + } + + $parameters ??= $this->resolveParameters($event->controllerArgumentsEvent, $attribute->vars); + $status = 200; + + foreach ($parameters as $k => $v) { + if (!$v instanceof FormInterface) { + continue; + } + if ($v->isSubmitted() && !$v->isValid()) { + $status = 422; + } + $parameters[$k] = $v->createView(); + } + + $event->setResponse($attribute->stream + ? new StreamedResponse(fn () => $this->twig->display($attribute->template, $parameters), $status) + : new Response($this->twig->render($attribute->template, $parameters), $status) + ); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::VIEW => ['onKernelView', -128], + ]; + } + + private function resolveParameters(ControllerArgumentsEvent $event, ?array $vars): array + { + if ([] === $vars) { + return []; + } + + $parameters = $event->getNamedArguments(); + + if (null !== $vars) { + $parameters = array_intersect_key($parameters, array_flip($vars)); + } + + return $parameters; + } +} diff --git a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php index 3e12371ba99c3..feb25ed5c2062 100644 --- a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php @@ -29,9 +29,6 @@ public function __construct(Packages $packages) $this->packages = $packages; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index af6ea706b280c..dd2e0682d2901 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -33,9 +33,6 @@ public function __construct(string|FileLinkFormatter $fileLinkFormat, string $pr $this->charset = $charset; } - /** - * {@inheritdoc} - */ public function getFilters(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/CsrfExtension.php b/src/Symfony/Bridge/Twig/Extension/CsrfExtension.php index 646a48798ba6d..951fc31d7e06b 100644 --- a/src/Symfony/Bridge/Twig/Extension/CsrfExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CsrfExtension.php @@ -20,9 +20,6 @@ */ final class CsrfExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php index 910fece83b9bb..c84e1e751a0f8 100644 --- a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php @@ -35,9 +35,6 @@ public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null) $this->dumper = $dumper; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ @@ -45,9 +42,6 @@ public function getFunctions(): array ]; } - /** - * {@inheritdoc} - */ public function getTokenParsers(): array { return [new DumpTokenParser()]; @@ -74,7 +68,7 @@ public function dump(Environment $env, array $context): ?string } $dump = fopen('php://memory', 'r+'); - $this->dumper = $this->dumper ?? new HtmlDumper(); + $this->dumper ??= new HtmlDumper(); $this->dumper->setCharset($env->getCharset()); foreach ($vars as $value) { diff --git a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php index a30499620ea0f..49e4c95cbbba2 100644 --- a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php @@ -22,9 +22,6 @@ */ final class ExpressionExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index c041a51f9c300..827145963a8e6 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -40,9 +40,6 @@ public function __construct(TranslatorInterface $translator = null) $this->translator = $translator; } - /** - * {@inheritdoc} - */ public function getTokenParsers(): array { return [ @@ -51,9 +48,6 @@ public function getTokenParsers(): array ]; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ @@ -77,9 +71,6 @@ public function getFunctions(): array ]; } - /** - * {@inheritdoc} - */ public function getFilters(): array { return [ @@ -88,9 +79,6 @@ public function getFilters(): array ]; } - /** - * {@inheritdoc} - */ public function getTests(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php index 104bd3231f773..938d3ddabf256 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php @@ -30,9 +30,6 @@ public function __construct(UrlHelper $urlHelper) $this->urlHelper = $urlHelper; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php index 1af9ddb23cf51..072d890deb476 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php @@ -22,9 +22,6 @@ */ final class HttpKernelExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php index 3ac7582b8fbeb..abced287f999c 100644 --- a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php @@ -29,9 +29,6 @@ public function __construct(LogoutUrlGenerator $generator) $this->generator = $generator; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php index 3b3a2ac58e27b..a22ad14d50a74 100644 --- a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php @@ -32,9 +32,6 @@ public function __construct(UrlGeneratorInterface $generator) $this->generator = $generator; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php index dd1c57fb3b36e..25d1cab2cfa9f 100644 --- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php @@ -69,9 +69,6 @@ public function getImpersonateExitPath(string $exitTo = null): string return $this->impersonateUrlGenerator->generateExitPath($exitTo); } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php index cbb7394e7d848..7891dab1e382b 100644 --- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php @@ -58,9 +58,6 @@ public function getTranslator(): TranslatorInterface return $this->translator; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ @@ -68,9 +65,6 @@ public function getFunctions(): array ]; } - /** - * {@inheritdoc} - */ public function getFilters(): array { return [ @@ -78,9 +72,6 @@ public function getFilters(): array ]; } - /** - * {@inheritdoc} - */ public function getTokenParsers(): array { return [ @@ -92,9 +83,6 @@ public function getTokenParsers(): array ]; } - /** - * {@inheritdoc} - */ public function getNodeVisitors(): array { return [$this->getTranslationNodeVisitor(), new TranslationDefaultDomainNodeVisitor()]; diff --git a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php index fcc34ecb5aad6..11eca517c5d69 100644 --- a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php @@ -31,9 +31,6 @@ public function __construct(RequestStack $requestStack) $this->requestStack = $requestStack; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php index 895faf457f648..71f556aead03e 100644 --- a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @@ -32,9 +32,6 @@ public function __construct(Registry $workflowRegistry) $this->workflowRegistry = $workflowRegistry; } - /** - * {@inheritdoc} - */ public function getFunctions(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php index 2d0595b7e9bca..e265dbfa9e7e7 100644 --- a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php @@ -22,9 +22,6 @@ */ final class YamlExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFilters(): array { return [ diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php index 6f408ebb584ad..7f733455e824a 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php @@ -30,9 +30,6 @@ public function __construct(array $defaultThemes, Environment $environment) $this->environment = $environment; } - /** - * {@inheritdoc} - */ public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []): string { $cacheKey = $view->vars[self::CACHE_KEY_VAR]; diff --git a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php index 85e0ba93d42c9..397b35f1569d2 100644 --- a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php +++ b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php @@ -11,9 +11,12 @@ namespace Symfony\Bridge\Twig\Mime; -use League\HTMLToMarkdown\HtmlConverter; +use League\HTMLToMarkdown\HtmlConverterInterface; use Symfony\Component\Mime\BodyRendererInterface; use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\HtmlToTextConverter\DefaultHtmlToTextConverter; +use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; +use Symfony\Component\Mime\HtmlToTextConverter\LeagueHtmlToMarkdownConverter; use Symfony\Component\Mime\Message; use Twig\Environment; @@ -24,19 +27,13 @@ final class BodyRenderer implements BodyRendererInterface { private Environment $twig; private array $context; - private HtmlConverter $converter; + private HtmlToTextConverterInterface $converter; - public function __construct(Environment $twig, array $context = []) + public function __construct(Environment $twig, array $context = [], HtmlToTextConverterInterface $converter = null) { $this->twig = $twig; $this->context = $context; - if (class_exists(HtmlConverter::class)) { - $this->converter = new HtmlConverter([ - 'hard_break' => true, - 'strip_tags' => true, - 'remove_nodes' => 'head style', - ]); - } + $this->converter = $converter ?: (interface_exists(HtmlConverterInterface::class) ? new LeagueHtmlToMarkdownConverter() : new DefaultHtmlToTextConverter()); } public function render(Message $message): void @@ -45,15 +42,13 @@ public function render(Message $message): void return; } - $messageContext = $message->getContext(); - - $previousRenderingKey = $messageContext[__CLASS__] ?? null; - unset($messageContext[__CLASS__]); - $currentRenderingKey = $this->getFingerPrint($message); - if ($previousRenderingKey === $currentRenderingKey) { + if (null === $message->getTextTemplate() && null === $message->getHtmlTemplate()) { + // email has already been rendered return; } + $messageContext = $message->getContext(); + if (isset($messageContext['email'])) { throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message))); } @@ -64,42 +59,20 @@ public function render(Message $message): void if ($template = $message->getTextTemplate()) { $message->text($this->twig->render($template, $vars)); + $message->textTemplate(null); } if ($template = $message->getHtmlTemplate()) { $message->html($this->twig->render($template, $vars)); + $message->htmlTemplate(null); } + $message->context([]); + // if text body is empty, compute one from the HTML body if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) { - $message->text($this->convertHtmlToText(\is_resource($html) ? stream_get_contents($html) : $html)); - } - $message->context($message->getContext() + [__CLASS__ => $currentRenderingKey]); - } - - private function getFingerPrint(TemplatedEmail $message): string - { - $messageContext = $message->getContext(); - unset($messageContext[__CLASS__]); - - $payload = [$messageContext, $message->getTextTemplate(), $message->getHtmlTemplate()]; - try { - $serialized = serialize($payload); - } catch (\Exception) { - // Serialization of 'Closure' is not allowed - // Happens when context contain a closure, in that case, we assume that context always change. - $serialized = random_bytes(8); + $text = $this->converter->convert(\is_resource($html) ? stream_get_contents($html) : $html, $message->getHtmlCharset()); + $message->text($text, $message->getHtmlCharset()); } - - return md5($serialized); - } - - private function convertHtmlToText(string $html): string - { - if (isset($this->converter)) { - return $this->converter->convert($html); - } - - return strip_tags(preg_replace('{<(head|style)\b.*?}is', '', $html)); } } diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index 60a29304c9b20..215b53a03f2a4 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -14,6 +14,7 @@ use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\Mime\Header\Headers; use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\Part\DataPart; use Twig\Extra\CssInliner\CssInlinerExtension; use Twig\Extra\Inky\InkyExtension; use Twig\Extra\Markdown\MarkdownExtension; @@ -134,7 +135,7 @@ public function exception(\Throwable|FlattenException $exception): static $exceptionAsString = $this->getExceptionAsString($exception); $this->context['exception'] = true; - $this->attach($exceptionAsString, 'exception.txt', 'text/plain'); + $this->addPart(new DataPart($exceptionAsString, 'exception.txt', 'text/plain')); $this->importance(self::IMPORTANCE_URGENT); if (!$this->getSubject()) { @@ -208,7 +209,7 @@ private function getExceptionAsString(\Throwable|FlattenException $exception): s return $exception->getAsString(); } - $message = \get_class($exception); + $message = $exception::class; if ('' !== $exception->getMessage()) { $message .= ': '.$exception->getMessage(); } diff --git a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php index e0b3bef29308f..1d3b92d6dbdd6 100644 --- a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php @@ -12,6 +12,8 @@ namespace Symfony\Bridge\Twig\Mime; use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Part\BodyFile; +use Symfony\Component\Mime\Part\DataPart; use Twig\Environment; /** @@ -38,11 +40,8 @@ public function toName(): string public function image(string $image, string $contentType = null): string { $file = $this->twig->getLoader()->getSourceContext($image); - if ($path = $file->getPath()) { - $this->message->embedFromPath($path, $image, $contentType); - } else { - $this->message->embed($file->getCode(), $image, $contentType); - } + $body = $file->getPath() ? new BodyFile($file->getPath()) : $file->getCode(); + $this->message->addPart((new DataPart($body, $image, $contentType))->asInline()); return 'cid:'.$image; } @@ -50,11 +49,8 @@ public function image(string $image, string $contentType = null): string public function attach(string $file, string $name = null, string $contentType = null): void { $file = $this->twig->getLoader()->getSourceContext($file); - if ($path = $file->getPath()) { - $this->message->attachFromPath($path, $name, $contentType); - } else { - $this->message->attach($file->getCode(), $name, $contentType); - } + $body = $file->getPath() ? new BodyFile($file->getPath()) : $file->getCode(); + $this->message->addPart(new DataPart($body, $name, $contentType)); } /** diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 61c8b5ff52083..d0e3337a9239c 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -37,9 +37,6 @@ public function __construct() $this->scope = new Scope(); } - /** - * {@inheritdoc} - */ protected function doEnterNode(Node $node, Environment $env): Node { if ($node instanceof BlockNode || $node instanceof ModuleNode) { @@ -86,9 +83,6 @@ protected function doEnterNode(Node $node, Environment $env): Node return $node; } - /** - * {@inheritdoc} - */ protected function doLeaveNode(Node $node, Environment $env): ?Node { if ($node instanceof TransDefaultDomainNode) { @@ -102,9 +96,6 @@ protected function doLeaveNode(Node $node, Environment $env): ?Node return $node; } - /** - * {@inheritdoc} - */ public function getPriority(): int { return -10; diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php index c8bee150982df..29cb13d0b2e5b 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php @@ -49,9 +49,6 @@ public function getMessages(): array return $this->messages; } - /** - * {@inheritdoc} - */ protected function doEnterNode(Node $node, Environment $env): Node { if (!$this->enabled) { @@ -101,17 +98,11 @@ protected function doEnterNode(Node $node, Environment $env): Node return $node; } - /** - * {@inheritdoc} - */ protected function doLeaveNode(Node $node, Environment $env): ?Node { return $node; } - /** - * {@inheritdoc} - */ public function getPriority(): int { return 0; diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig index 0e80840541fa1..4bf2f9cde1ddd 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig @@ -233,30 +233,8 @@ {% if required -%} {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} {%- endif -%} - {% if label is empty -%} - {%- if label_format is not empty -%} - {% set label = label_format|replace({ - '%name%': name, - '%id%': id, - }) %} - {%- else -%} - {% set label = name|humanize %} - {%- endif -%} - {%- endif -%} <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> - {%- if translation_domain is same as(false) -%} - {%- if label_html is same as(false) -%} - {{- label -}} - {%- else -%} - {{- label|raw -}} - {%- endif -%} - {%- else -%} - {%- if label_html is same as(false) -%} - {{- label|trans(label_translation_parameters, translation_domain) -}} - {%- else -%} - {{- label|trans(label_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_label_content') -}} {% block form_label_errors %}{{- form_errors(form) -}}{% endblock form_label_errors %} {%- else -%} {%- if errors|length > 0 -%} @@ -283,33 +261,11 @@ {%- if required -%} {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} {%- endif -%} - {%- if label is not same as(false) and label is empty -%} - {%- if label_format is not empty -%} - {%- set label = label_format|replace({ - '%name%': name, - '%id%': id, - }) -%} - {%- else -%} - {%- set label = name|humanize -%} - {%- endif -%} - {%- endif -%} {{ widget|raw }} {%- if label is not same as(false) -%} - {%- if translation_domain is same as(false) -%} - {%- if label_html is same as(false) -%} - {{- label -}} - {%- else -%} - {{- label|raw -}} - {%- endif -%} - {%- else -%} - {%- if label_html is same as(false) -%} - {{- label|trans(label_translation_parameters, translation_domain) -}} - {%- else -%} - {{- label|trans(label_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_label_content') -}} {%- endif -%} {{- form_errors(form) -}} @@ -353,19 +309,7 @@ {%- if help is not empty -%} {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' form-text text-muted')|trim}) -%} - {%- if translation_domain is same as(false) -%} - {%- if help_html is same as(false) -%} - {{- help -}} - {%- else -%} - {{- help|raw -}} - {%- endif -%} - {%- else -%} - {%- if help_html is same as(false) -%} - {{- help|trans(help_translation_parameters, translation_domain) -}} - {%- else -%} - {{- help|trans(help_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_help_content') -}} {%- endif -%} {%- endblock form_help %} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig index 54903a5713082..1734452334ea9 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig @@ -209,30 +209,48 @@ {%- endblock submit_widget %} {%- block checkbox_widget -%} - {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} + {%- set attr_class = attr_class|default(attr.class|default('')) -%} + {%- set row_class = '' -%} + {%- if 'btn-check' not in attr_class -%} + {%- set attr_class = attr_class ~ ' form-check-input' -%} + {%- set row_class = 'form-check' -%} + {%- endif -%} + {%- set attr = attr|merge({class: attr_class|trim}) -%} {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} - {%- set row_class = 'form-check' -%} {%- if 'checkbox-inline' in parent_label_class %} {%- set row_class = row_class ~ ' form-check-inline' -%} {% endif -%} {%- if 'checkbox-switch' in parent_label_class %} {%- set row_class = row_class ~ ' form-switch' -%} {% endif -%} -
- {{- form_label(form, null, { widget: parent() }) -}} -
+ {%- if row_class is not empty -%} +
+ {%- endif -%} + {{- form_label(form, null, { widget: parent() }) -}} + {%- if row_class is not empty -%} +
+ {%- endif -%} {%- endblock checkbox_widget %} {%- block radio_widget -%} - {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} + {%- set attr_class = attr_class|default(attr.class|default('')) -%} + {%- set row_class = '' -%} + {%- if 'btn-check' not in attr_class -%} + {%- set attr_class = attr_class ~ ' form-check-input' -%} + {%- set row_class = 'form-check' -%} + {%- endif -%} + {%- set attr = attr|merge({class: attr_class|trim}) -%} {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} - {%- set row_class = 'form-check' -%} {%- if 'radio-inline' in parent_label_class -%} {%- set row_class = row_class ~ ' form-check-inline' -%} {%- endif -%} -
- {{- form_label(form, null, { widget: parent() }) -}} -
+ {%- if row_class is not empty -%} +
+ {%- endif -%} + {{- form_label(form, null, { widget: parent() }) -}} + {%- if row_class is not empty -%} +
+ {%- endif -%} {%- endblock radio_widget %} {%- block choice_widget_collapsed -%} @@ -276,7 +294,11 @@ {%- block checkbox_radio_label -%} {#- Do not display the label if widget is not defined in order to prevent double label rendering -#} {%- if widget is defined -%} - {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%} + {%- set label_attr_class = label_attr_class|default(label_attr.class|default('')) -%} + {%- if 'btn' not in label_attr_class -%} + {%- set label_attr_class = label_attr_class ~ ' form-check-label' -%} + {%- endif -%} + {%- set label_attr = label_attr|merge({class: label_attr_class|trim}) -%} {%- if not compound -%} {% set label_attr = label_attr|merge({'for': id}) %} {%- endif -%} @@ -286,33 +308,11 @@ {%- if parent_label_class is defined -%} {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%} {%- endif -%} - {%- if label is not same as(false) and label is empty -%} - {%- if label_format is not empty -%} - {%- set label = label_format|replace({ - '%name%': name, - '%id%': id, - }) -%} - {%- else -%} - {%- set label = name|humanize -%} - {%- endif -%} - {%- endif -%} {{ widget|raw }} {%- if label is not same as(false) -%} - {%- if translation_domain is same as(false) -%} - {%- if label_html is same as(false) -%} - {{- label -}} - {%- else -%} - {{- label|raw -}} - {%- endif -%} - {%- else -%} - {%- if label_html is same as(false) -%} - {{- label|trans(label_translation_parameters, translation_domain) -}} - {%- else -%} - {{- label|trans(label_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_label_content') -}} {%- endif -%} {%- endif -%} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 4769585c22772..61f64e33fa751 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -290,34 +290,38 @@ {% if required -%} {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} {%- endif -%} - {% if label is empty -%} - {%- if label_format is not empty -%} - {% set label = label_format|replace({ - '%name%': name, - '%id%': id, - }) %} - {%- else -%} - {% set label = name|humanize %} - {%- endif -%} - {%- endif -%} <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> - {%- if translation_domain is same as(false) -%} - {%- if label_html is same as(false) -%} - {{- label -}} - {%- else -%} - {{- label|raw -}} - {%- endif -%} - {%- else -%} - {%- if label_html is same as(false) -%} - {{- label|trans(label_translation_parameters, translation_domain) -}} - {%- else -%} - {{- label|trans(label_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_label_content') -}} {%- endif -%} {%- endblock form_label -%} +{%- block form_label_content -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{- label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{- label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +{%- endblock form_label_content -%} + {%- block button_label -%}{%- endblock -%} {# Help #} @@ -326,23 +330,27 @@ {%- if help is not empty -%} {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-text')|trim}) -%}
- {%- if translation_domain is same as(false) -%} - {%- if help_html is same as(false) -%} - {{- help -}} - {%- else -%} - {{- help|raw -}} - {%- endif -%} - {%- else -%} - {%- if help_html is same as(false) -%} - {{- help|trans(help_translation_parameters, translation_domain) -}} - {%- else -%} - {{- help|trans(help_translation_parameters, translation_domain)|raw -}} - {%- endif -%} - {%- endif -%} + {{- block('form_help_content') -}}
{%- endif -%} {%- endblock form_help %} +{% block form_help_content -%} + {%- if translation_domain is same as(false) -%} + {%- if help_html is same as(false) -%} + {{- help -}} + {%- else -%} + {{- help|raw -}} + {%- endif -%} + {%- else -%} + {%- if help_html is same as(false) -%} + {{- help|trans(help_translation_parameters, translation_domain) -}} + {%- else -%} + {{- help|trans(help_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +{%- endblock form_help_content %} + {# Rows #} {%- block repeated_row -%} diff --git a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php index 6ba4ca77e51c4..01db44528a0d4 100644 --- a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php +++ b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php @@ -228,6 +228,40 @@ public function testGetFlashes() ); } + public function testGetCurrentRoute() + { + $this->setRequestStack(new Request(attributes: ['_route' => 'some_route'])); + + $this->assertSame('some_route', $this->appVariable->getCurrent_Route()); + } + + public function testGetCurrentRouteWithRequestStackNotSet() + { + $this->expectException(\RuntimeException::class); + $this->appVariable->getCurrent_Route(); + } + + public function testGetCurrentRouteParameters() + { + $routeParams = ['some_param' => true]; + $this->setRequestStack(new Request(attributes: ['_route_params' => $routeParams])); + + $this->assertSame($routeParams, $this->appVariable->getCurrent_Route_Parameters()); + } + + public function testGetCurrentRouteParametersWithoutAttribute() + { + $this->setRequestStack(new Request()); + + $this->assertSame([], $this->appVariable->getCurrent_Route_Parameters()); + } + + public function testGetCurrentRouteParametersWithRequestStackNotSet() + { + $this->expectException(\RuntimeException::class); + $this->appVariable->getCurrent_Route_Parameters(); + } + protected function setRequestStack($request) { $requestStackMock = $this->createMock(RequestStack::class); diff --git a/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php b/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php new file mode 100644 index 0000000000000..8f6d6a2ca4068 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Attribute\Template; +use Symfony\Bridge\Twig\EventListener\TemplateAttributeListener; +use Symfony\Bridge\Twig\Tests\Fixtures\TemplateAttributeController; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ViewEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Twig\Environment; + +class TemplateAttributeListenerTest extends TestCase +{ + public function testAttribute() + { + $twig = $this->createMock(Environment::class); + $twig->expects($this->exactly(3)) + ->method('render') + ->withConsecutive( + ['templates/foo.html.twig', ['foo' => 'bar']], + ['templates/foo.html.twig', ['bar' => 'Bar', 'buz' => 'def']], + ['templates/foo.html.twig', []], + ) + ->willReturn('Bar'); + + $request = new Request(); + $kernel = $this->createMock(HttpKernelInterface::class); + $controllerArgumentsEvent = new ControllerArgumentsEvent($kernel, [new TemplateAttributeController(), 'foo'], ['Bar'], $request, null); + $listener = new TemplateAttributeListener($twig); + + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, ['foo' => 'bar'], $controllerArgumentsEvent); + $listener->onKernelView($event); + $this->assertSame('Bar', $event->getResponse()->getContent()); + + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, null, $controllerArgumentsEvent); + $listener->onKernelView($event); + $this->assertSame('Bar', $event->getResponse()->getContent()); + + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, null); + $listener->onKernelView($event); + $this->assertNull($event->getResponse()); + + $request->attributes->set('_template', new Template('templates/foo.html.twig')); + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, []); + $listener->onKernelView($event); + $this->assertSame('Bar', $event->getResponse()->getContent()); + } + + public function testForm() + { + $request = new Request(); + $kernel = $this->createMock(HttpKernelInterface::class); + $controllerArgumentsEvent = new ControllerArgumentsEvent($kernel, [new TemplateAttributeController(), 'foo'], [], $request, null); + $listener = new TemplateAttributeListener($this->createMock(Environment::class)); + + $form = $this->createMock(FormInterface::class); + $form->expects($this->once())->method('createView'); + $form->expects($this->once())->method('isSubmitted')->willReturn(true); + $form->expects($this->once())->method('isValid')->willReturn(false); + + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, ['bar' => $form], $controllerArgumentsEvent); + $listener->onKernelView($event); + + $this->assertSame(422, $event->getResponse()->getStatusCode()); + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php index 1403372bc5599..7ac695ec927c3 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php @@ -404,6 +404,21 @@ public function testCheckboxSwitchWithValue() ); } + public function testCheckboxToggleWithValue() + { + $form = $this->factory->createNamed('name', CheckboxType::class, false, [ + 'value' => 'foo&bar', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['id' => 'my&id', 'attr' => ['class' => 'btn-check my&class'], 'label_attr' => ['class' => 'btn btn-primary']], + '/input[@type="checkbox"][@name="name"][@id="my&id"][@class="btn-check my&class"][@value="foo&bar"] + /following-sibling::label + [@class="btn btn-primary required"] + [.="[trans]Name[/trans]"] +' + ); + } + public function testMultipleChoiceSkipsPlaceholder() { $form = $this->factory->createNamed('name', ChoiceType::class, ['&a'], [ diff --git a/src/Symfony/Bridge/Twig/Tests/Fixtures/TemplateAttributeController.php b/src/Symfony/Bridge/Twig/Tests/Fixtures/TemplateAttributeController.php new file mode 100644 index 0000000000000..3e69e0d2466cf --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Fixtures/TemplateAttributeController.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Fixtures; + +use Symfony\Bridge\Twig\Attribute\Template; + +class TemplateAttributeController +{ + #[Template('templates/foo.html.twig', vars: ['bar', 'buz'])] + public function foo($bar, $baz = 'abc', $buz = 'def') + { + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php index 8ff343b684b5e..231067e0365dc 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php @@ -15,6 +15,8 @@ use Symfony\Bridge\Twig\Mime\BodyRenderer; use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\HtmlToTextConverter\DefaultHtmlToTextConverter; +use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; use Symfony\Component\Mime\Part\Multipart\AlternativePart; use Twig\Environment; use Twig\Loader\ArrayLoader; @@ -27,14 +29,24 @@ public function testRenderTextOnly() $this->assertEquals('Text', $email->getBody()->bodyToString()); } - public function testRenderHtmlOnly() + public function testRenderHtmlOnlyWithDefaultConverter() { - $html = 'headHTML'; - $email = $this->prepareEmail(null, $html); + $html = 'HTML'; + $email = $this->prepareEmail(null, $html, [], new DefaultHtmlToTextConverter()); $body = $email->getBody(); $this->assertInstanceOf(AlternativePart::class, $body); $this->assertEquals('HTML', $body->getParts()[0]->bodyToString()); - $this->assertEquals(str_replace('=', '=3D', $html), $body->getParts()[1]->bodyToString()); + $this->assertEquals(str_replace(['=', "\n"], ['=3D', "\r\n"], $html), $body->getParts()[1]->bodyToString()); + } + + public function testRenderHtmlOnlyWithLeagueConverter() + { + $html = 'HTML'; + $email = $this->prepareEmail(null, $html); + $body = $email->getBody(); + $this->assertInstanceOf(AlternativePart::class, $body); + $this->assertEquals('**HTML**', $body->getParts()[0]->bodyToString()); + $this->assertEquals(str_replace(['=', "\n"], ['=3D', "\r\n"], $html), $body->getParts()[1]->bodyToString()); } public function testRenderMultiLineHtmlOnly() @@ -50,7 +62,7 @@ public function testRenderMultiLineHtmlOnly() $email = $this->prepareEmail(null, $html); $body = $email->getBody(); $this->assertInstanceOf(AlternativePart::class, $body); - $this->assertEquals('HTML', str_replace(["\r", "\n"], '', $body->getParts()[0]->bodyToString())); + $this->assertEquals('**HTML**', str_replace(["\r", "\n"], '', $body->getParts()[0]->bodyToString())); $this->assertEquals(str_replace(['=', "\n"], ['=3D', "\r\n"], $html), $body->getParts()[1]->bodyToString()); } @@ -121,7 +133,7 @@ public function testRenderedOnceUnserializableContext() $this->assertEquals('Text', $email->getTextBody()); } - private function prepareEmail(?string $text, ?string $html, array $context = []): TemplatedEmail + private function prepareEmail(?string $text, ?string $html, array $context = [], HtmlToTextConverterInterface $converter = null): TemplatedEmail { $twig = new Environment(new ArrayLoader([ 'text' => $text, @@ -129,7 +141,7 @@ private function prepareEmail(?string $text, ?string $html, array $context = []) 'document.txt' => 'Some text document...', 'image.jpg' => 'Some image data', ])); - $renderer = new BodyRenderer($twig); + $renderer = new BodyRenderer($twig, [], $converter); $email = (new TemplatedEmail()) ->to('fabien@symfony.com') ->from('helene@symfony.com') diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php index 77548fb119626..019be16ff4bcf 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Mime\TemplatedEmail; +use Symfony\Component\Mime\Part\DataPart; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; @@ -58,7 +59,7 @@ public function testSymfonySerialize() $e->textTemplate('email.txt.twig'); $e->htmlTemplate('email.html.twig'); $e->context(['foo' => 'bar']); - $e->attach('Some Text file', 'test.txt'); + $e->addPart(new DataPart('Some Text file', 'test.txt')); $expected = clone $e; $expectedJson = <<serialize($e, 'json', [ObjectNormalizer::IGNORED_ATTRIBUTES => ['cachedBody']]); - $this->assertSame($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + $this->assertStringMatchesFormat($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); $n = $serializer->deserialize($serialized, TemplatedEmail::class, 'json'); $serialized = $serializer->serialize($e, 'json', [ObjectNormalizer::IGNORED_ATTRIBUTES => ['cachedBody']]); - $this->assertSame($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + $this->assertStringMatchesFormat($expectedJson, json_encode(json_decode($serialized), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); $n->from('fabien@symfony.com'); $expected->from('fabien@symfony.com'); diff --git a/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php index 341dc41855ab0..d4996dbe91cd1 100644 --- a/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php +++ b/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php @@ -29,9 +29,6 @@ */ final class DumpTokenParser extends AbstractTokenParser { - /** - * {@inheritdoc} - */ public function parse(Token $token): Node { $values = null; @@ -43,9 +40,6 @@ public function parse(Token $token): Node return new DumpNode($this->parser->getVarName(), $values, $token->getLine(), $this->getTag()); } - /** - * {@inheritdoc} - */ public function getTag(): string { return 'dump'; diff --git a/src/Symfony/Bridge/Twig/TokenParser/FormThemeTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/FormThemeTokenParser.php index ef5dacb59ddd1..b95a2a05e76a4 100644 --- a/src/Symfony/Bridge/Twig/TokenParser/FormThemeTokenParser.php +++ b/src/Symfony/Bridge/Twig/TokenParser/FormThemeTokenParser.php @@ -24,9 +24,6 @@ */ final class FormThemeTokenParser extends AbstractTokenParser { - /** - * {@inheritdoc} - */ public function parse(Token $token): Node { $lineno = $token->getLine(); @@ -54,9 +51,6 @@ public function parse(Token $token): Node return new FormThemeNode($form, $resources, $lineno, $this->getTag(), $only); } - /** - * {@inheritdoc} - */ public function getTag(): string { return 'form_theme'; diff --git a/src/Symfony/Bridge/Twig/TokenParser/TransDefaultDomainTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/TransDefaultDomainTokenParser.php index 19b820497d9fa..c6d850d07cbf7 100644 --- a/src/Symfony/Bridge/Twig/TokenParser/TransDefaultDomainTokenParser.php +++ b/src/Symfony/Bridge/Twig/TokenParser/TransDefaultDomainTokenParser.php @@ -23,9 +23,6 @@ */ final class TransDefaultDomainTokenParser extends AbstractTokenParser { - /** - * {@inheritdoc} - */ public function parse(Token $token): Node { $expr = $this->parser->getExpressionParser()->parseExpression(); @@ -35,9 +32,6 @@ public function parse(Token $token): Node return new TransDefaultDomainNode($expr, $token->getLine(), $this->getTag()); } - /** - * {@inheritdoc} - */ public function getTag(): string { return 'trans_default_domain'; diff --git a/src/Symfony/Bridge/Twig/TokenParser/TransTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/TransTokenParser.php index b440931bba794..e60263a4a783f 100644 --- a/src/Symfony/Bridge/Twig/TokenParser/TransTokenParser.php +++ b/src/Symfony/Bridge/Twig/TokenParser/TransTokenParser.php @@ -27,9 +27,6 @@ */ final class TransTokenParser extends AbstractTokenParser { - /** - * {@inheritdoc} - */ public function parse(Token $token): Node { $lineno = $token->getLine(); @@ -85,9 +82,6 @@ public function decideTransFork(Token $token): bool return $token->test(['endtrans']); } - /** - * {@inheritdoc} - */ public function getTag(): string { return 'trans'; diff --git a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php index 86ef269328530..0707359dd16cf 100644 --- a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php +++ b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php @@ -45,9 +45,6 @@ public function __construct(Environment $twig) $this->twig = $twig; } - /** - * {@inheritdoc} - */ public function extract($resource, MessageCatalogue $catalogue) { foreach ($this->extractFiles($resource) as $file) { @@ -59,9 +56,6 @@ public function extract($resource, MessageCatalogue $catalogue) } } - /** - * {@inheritdoc} - */ public function setPrefix(string $prefix) { $this->prefix = $prefix; @@ -86,9 +80,6 @@ protected function canBeExtracted(string $file): bool return $this->isFile($file) && 'twig' === pathinfo($file, \PATHINFO_EXTENSION); } - /** - * {@inheritdoc} - */ protected function extractFromDirectory($directory): iterable { $finder = new Finder(); diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index f182dfa406417..5a4f501ebce42 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -23,6 +23,7 @@ "require-dev": { "doctrine/annotations": "^1.12", "egulias/email-validator": "^2.1.10|^3", + "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/asset": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", @@ -30,9 +31,9 @@ "symfony/form": "^6.1", "symfony/html-sanitizer": "^6.1", "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", "symfony/intl": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", + "symfony/mime": "^6.2", "symfony/polyfill-intl-icu": "~1.0", "symfony/property-info": "^5.4|^6.0", "symfony/routing": "^5.4|^6.0", @@ -42,7 +43,7 @@ "symfony/security-core": "^5.4|^6.0", "symfony/security-csrf": "^5.4|^6.0", "symfony/security-http": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0", + "symfony/serializer": "^6.2", "symfony/stopwatch": "^5.4|^6.0", "symfony/console": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", @@ -58,7 +59,8 @@ "symfony/console": "<5.4", "symfony/form": "<6.1", "symfony/http-foundation": "<5.4", - "symfony/http-kernel": "<5.4", + "symfony/http-kernel": "<6.2", + "symfony/mime": "<6.2", "symfony/translation": "<5.4", "symfony/workflow": "<5.4" }, diff --git a/src/Symfony/Bundle/DebugBundle/DebugBundle.php b/src/Symfony/Bundle/DebugBundle/DebugBundle.php index 04fd507612747..045ea5f8dbd65 100644 --- a/src/Symfony/Bundle/DebugBundle/DebugBundle.php +++ b/src/Symfony/Bundle/DebugBundle/DebugBundle.php @@ -44,9 +44,6 @@ public function boot() } } - /** - * {@inheritdoc} - */ public function build(ContainerBuilder $container) { parent::build($container); diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php index 2579bf64eab13..5c531cc91dbcd 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Compiler/DumpDataCollectorPass.php @@ -22,9 +22,6 @@ */ class DumpDataCollectorPass implements CompilerPassInterface { - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('data_collector.dump')) { diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php index f645a48f8a3b1..caf7359690750 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php @@ -21,9 +21,6 @@ */ class Configuration implements ConfigurationInterface { - /** - * {@inheritdoc} - */ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('debug'); diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php index 731cc62b3116d..2d077ba681f5e 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php @@ -29,9 +29,6 @@ */ class DebugExtension extends Extension { - /** - * {@inheritdoc} - */ public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); @@ -97,17 +94,11 @@ public function load(array $configs, ContainerBuilder $container) } } - /** - * {@inheritdoc} - */ public function getXsdValidationBasePath(): string|false { return __DIR__.'/../Resources/config/schema'; } - /** - * {@inheritdoc} - */ public function getNamespace(): string { return 'http://symfony.com/schema/dic/debug'; diff --git a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/icon.svg b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/icon.svg index d6cedd8a19dc3..9ba3a7d94d8f3 100644 --- a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/icon.svg +++ b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/icon.svg @@ -1 +1,9 @@ - + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 40698a59a6023..1d43a095ed1c5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,29 @@ CHANGELOG ========= +6.2 +--- + + * Add `resolve-env` option to `debug:config` command to display actual values of environment variables in dumped configuration + * Add `NotificationAssertionsTrait` + * Add option `framework.handle_all_throwables` to allow `Symfony\Component\HttpKernel\HttpKernel` to handle all kinds of `Throwable` + * Make `AbstractController::render()` able to deal with forms and deprecate `renderForm()` + * Deprecate the `Symfony\Component\Serializer\Normalizer\ObjectNormalizer` and + `Symfony\Component\Serializer\Normalizer\PropertyNormalizer` autowiring aliases, type-hint against + `Symfony\Component\Serializer\Normalizer\NormalizerInterface` or implement `NormalizerAwareInterface` instead + * Add service usages list to the `debug:container` command output + * Add service and alias deprecation message to `debug:container []` output + * Tag all workflows services with `workflow`, those with type=workflow are + tagged with `workflow.workflow`, and those with type=state_machine with + `workflow.state_machine` + * Add `rate_limiter` configuration option to `messenger.transport` to allow rate limited transports using the RateLimiter component + * Remove `@internal` tag from secret vaults to allow them to be used directly outside the framework bundle and custom vaults to be added + * Deprecate `framework.form.legacy_error_messages` config node + * Add a `framework.router.cache_dir` configuration option to configure the default `Router` `cache_dir` option + * Add option `framework.messenger.buses.*.default_middleware.allow_no_senders` to enable throwing when a message doesn't have a sender + * Deprecate `AbstractController::renderForm()`, use `render()` instead + * Deprecate `FrameworkExtension::registerRateLimiter()` + 6.1 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php index f72df9e6542bc..47b3db6d2c9d3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php @@ -29,17 +29,12 @@ public function __construct(string $phpArrayFile) $this->phpArrayFile = $phpArrayFile; } - /** - * {@inheritdoc} - */ public function isOptional(): bool { return true; } /** - * {@inheritdoc} - * * @return string[] A list of classes to preload on PHP 7.4+ */ public function warmUp(string $cacheDir): array diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php index 2fd5f661d2dee..5ecc25b3d7078 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php @@ -40,9 +40,6 @@ public function __construct(Reader $annotationReader, string $phpArrayFile, stri $this->debug = $debug; } - /** - * {@inheritdoc} - */ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter): bool { $annotatedClassPatterns = $cacheDir.'/annotations.map'; diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php index 41041aedaed99..cad71a409dbc0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php @@ -38,8 +38,6 @@ public function __construct(Psr6CacheClearer $poolClearer, array $pools = []) } /** - * {@inheritdoc} - * * @return string[] */ public function warmUp(string $cacheDirectory): array @@ -53,9 +51,6 @@ public function warmUp(string $cacheDirectory): array return []; } - /** - * {@inheritdoc} - */ public function isOptional(): bool { // optional cache warmers are not run when handling the request diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php index 95f62d9202203..3f4a8057ed187 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php @@ -38,8 +38,6 @@ public function __construct(KernelInterface $kernel, LoggerInterface $logger = n } /** - * {@inheritdoc} - * * @return string[] */ public function warmUp(string $cacheDir): array @@ -55,7 +53,7 @@ public function warmUp(string $cacheDir): array try { $this->dumpExtension($extension, $generator); } catch (\Exception $e) { - $this->logger?->warning('Failed to generate ConfigBuilder for extension {extensionClass}.', ['exception' => $e, 'extensionClass' => \get_class($extension)]); + $this->logger?->warning('Failed to generate ConfigBuilder for extension {extensionClass}.', ['exception' => $e, 'extensionClass' => $extension::class]); } } @@ -79,9 +77,6 @@ private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGener $generator->build($configuration); } - /** - * {@inheritdoc} - */ public function isOptional(): bool { return true; diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php index 21dd3a2728845..13daf436939e4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php @@ -34,9 +34,6 @@ public function __construct(ContainerInterface $container) $this->container = $container; } - /** - * {@inheritdoc} - */ public function warmUp(string $cacheDir): array { $router = $this->container->get('router'); @@ -48,17 +45,11 @@ public function warmUp(string $cacheDir): array throw new \LogicException(sprintf('The router "%s" cannot be warmed up because it does not implement "%s".', get_debug_type($router), WarmableInterface::class)); } - /** - * {@inheritdoc} - */ public function isOptional(): bool { return true; } - /** - * {@inheritdoc} - */ public static function getSubscribedServices(): array { return [ diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php index ccc1402e2ddfe..40093998293ce 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php @@ -39,9 +39,6 @@ public function __construct(array $loaders, string $phpArrayFile) $this->loaders = $loaders; } - /** - * {@inheritdoc} - */ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter): bool { if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php index 8cddae7e308ce..039658f4b721b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php @@ -34,8 +34,6 @@ public function __construct(ContainerInterface $container) } /** - * {@inheritdoc} - * * @return string[] */ public function warmUp(string $cacheDir): array @@ -49,17 +47,11 @@ public function warmUp(string $cacheDir): array return []; } - /** - * {@inheritdoc} - */ public function isOptional(): bool { return true; } - /** - * {@inheritdoc} - */ public static function getSubscribedServices(): array { return [ diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php index 04025a49bba66..ba0dede3948a2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php @@ -39,9 +39,6 @@ public function __construct(ValidatorBuilder $validatorBuilder, string $phpArray $this->validatorBuilder = $validatorBuilder; } - /** - * {@inheritdoc} - */ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter): bool { if (!method_exists($this->validatorBuilder, 'getLoaders')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php index ed5a47f12a0f7..f39950587c3e8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php @@ -31,9 +31,6 @@ #[AsCommand(name: 'about', description: 'Display information about the current project')] class AboutCommand extends Command { - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -47,9 +44,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -73,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int new TableSeparator(), ['Kernel'], new TableSeparator(), - ['Type', \get_class($kernel)], + ['Type', $kernel::class], ['Environment', $kernel->getEnvironment()], ['Debug', $kernel->isDebug() ? 'true' : 'false'], ['Charset', $kernel->getCharset()], @@ -87,8 +81,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int ['Architecture', (\PHP_INT_SIZE * 8).' bits'], ['Intl locale', class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a'], ['Timezone', date_default_timezone_get().' ('.(new \DateTime())->format(\DateTime::W3C).')'], - ['OPcache', \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'], - ['APCu', \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'], + ['OPcache', \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) ? 'true' : 'false'], + ['APCu', \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL) ? 'true' : 'false'], ['Xdebug', \extension_loaded('xdebug') ? 'true' : 'false'], ]; @@ -120,15 +114,15 @@ private static function formatFileSize(string $path): string private static function isExpired(string $date): bool { - $date = \DateTime::createFromFormat('d/m/Y', '01/'.$date); + $date = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.$date); - return false !== $date && new \DateTime() > $date->modify('last day of this month 23:59:59'); + return false !== $date && new \DateTimeImmutable() > $date->modify('last day of this month 23:59:59'); } private static function daysBeforeExpiration(string $date): string { - $date = \DateTime::createFromFormat('d/m/Y', '01/'.$date); + $date = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.$date); - return (new \DateTime())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days'); + return (new \DateTimeImmutable())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days'); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index aaad074daf246..2ee2d93988864 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -52,9 +52,6 @@ public function __construct(Filesystem $filesystem, string $projectDir) $this->projectDir = $projectDir; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -87,9 +84,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { /** @var KernelInterface $kernel */ diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php index 785027dbc8d4e..5ac0c8cbc2cb1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php @@ -44,7 +44,7 @@ protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilde $this->initializeBundles(); return $this->buildContainer(); - }, $kernel, \get_class($kernel)); + }, $kernel, $kernel::class); $container = $buildContainer(); $container->getCompilerPassConfig()->setRemovingPasses([]); $container->getCompilerPassConfig()->setAfterRemovingPasses([]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index b7d95cfec87f2..ca46a577154a3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -48,9 +48,6 @@ public function __construct(CacheClearerInterface $cacheClearer, Filesystem $fil $this->filesystem = $filesystem ?? new Filesystem(); } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -69,9 +66,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $fs = $this->filesystem; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php index 65da5d7709487..99ebf9a979595 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php @@ -45,9 +45,6 @@ public function __construct(Psr6CacheClearer $poolClearer, array $poolNames = nu $this->poolNames = $poolNames; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -63,9 +60,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php index 546bd631d492c..2c0dddd0fd9f9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php @@ -43,9 +43,6 @@ public function __construct(Psr6CacheClearer $poolClearer, array $poolNames = nu $this->poolNames = $poolNames; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -62,9 +59,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php index 8ad84bfa69503..a69624c8372c4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php @@ -41,9 +41,6 @@ public function __construct(ServiceProviderInterface $pools) $this->poolNames = array_keys($pools->getProvidedServices()); } - /** - * {@inheritdoc} - */ protected function configure(): void { $this @@ -59,9 +56,6 @@ protected function configure(): void ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php index 09ec8b1ef0cc7..f1e05b0db0768 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php @@ -37,9 +37,6 @@ public function __construct(array $poolNames) $this->poolNames = $poolNames; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -50,9 +47,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php index 1e8bb7f0338a6..53f2b01dff994 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php @@ -38,9 +38,6 @@ public function __construct(iterable $pools) $this->pools = $pools; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -53,9 +50,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php index 5e7dc36f206d8..48f7aa391bae4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php @@ -39,9 +39,6 @@ public function __construct(CacheWarmerAggregate $cacheWarmer) $this->cacheWarmer = $cacheWarmer; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -58,9 +55,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index 8e603648f9750..c5078d1b5b401 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -19,6 +19,7 @@ use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass; @@ -37,15 +38,13 @@ #[AsCommand(name: 'debug:config', description: 'Dump the current configuration for an extension')] class ConfigDebugCommand extends AbstractConfigCommand { - /** - * {@inheritdoc} - */ protected function configure() { $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), + new InputOption('resolve-env', null, InputOption::VALUE_NONE, 'Display resolved environment variable values instead of placeholders'), ]) ->setHelp(<<<'EOF' The %command.name% command dumps the current configuration for an @@ -65,9 +64,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -94,7 +90,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $extensionAlias = $extension->getAlias(); $container = $this->compileContainer(); - $config = $this->getConfig($extension, $container); + $config = $this->getConfig($extension, $container, $input->getOption('resolve-env')); if (null === $path = $input->getArgument('path')) { $io->title( @@ -210,12 +206,12 @@ private function getAvailableBundles(bool $alias): array return $availableBundles; } - private function getConfig(ExtensionInterface $extension, ContainerBuilder $container) + private function getConfig(ExtensionInterface $extension, ContainerBuilder $container, bool $resolveEnvs = false) { return $container->resolveEnvPlaceholders( $container->getParameterBag()->resolveValue( $this->getConfigForExtension($extension, $container) - ) + ), $resolveEnvs ?: null ); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index 271ba9bf6429b..628cb7cecf6c7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -39,9 +39,6 @@ #[AsCommand(name: 'config:dump-reference', description: 'Dump the default configuration for an extension')] class ConfigDumpReferenceCommand extends AbstractConfigCommand { - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -75,8 +72,6 @@ protected function configure() } /** - * {@inheritdoc} - * * @throws \LogicException */ protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index c5c2da4a4407c..b040f2419d1b3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -38,9 +38,6 @@ class ContainerDebugCommand extends Command { use BuildDebugContainerTrait; - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -114,9 +111,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -145,6 +139,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } elseif ($input->getOption('tags')) { $options = ['group_by' => 'tags']; } elseif ($tag = $input->getOption('tag')) { + $tag = $this->findProperTagName($input, $errorIo, $object, $tag); $options = ['tag' => $tag]; } elseif ($name = $input->getArgument('name')) { $name = $this->findProperServiceName($input, $errorIo, $object, $name, $input->getOption('show-hidden')); @@ -166,6 +161,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { $helper->describe($io, $object, $options); + if ('txt' === $options['format'] && isset($options['id'])) { + if ($object->hasDefinition($options['id'])) { + $definition = $object->getDefinition($options['id']); + if ($definition->isDeprecated()) { + $errorIo->warning($definition->getDeprecation($options['id'])['message'] ?? sprintf('The "%s" service is deprecated.', $options['id'])); + } + } + if ($object->hasAlias($options['id'])) { + $alias = $object->getAlias($options['id']); + if ($alias->isDeprecated()) { + $errorIo->warning($alias->getDeprecation($options['id'])['message'] ?? sprintf('The "%s" alias is deprecated.', $options['id'])); + } + } + } + if (isset($options['id']) && isset($kernel->getContainer()->getRemovedIds()[$options['id']])) { $errorIo->note(sprintf('The "%s" service or alias has been removed or inlined when the container was compiled.', $options['id'])); } @@ -261,7 +271,7 @@ private function findProperServiceName(InputInterface $input, SymfonyStyle $io, } $matchingServices = $this->findServiceIdsContaining($builder, $name, $showHidden); - if (empty($matchingServices)) { + if (!$matchingServices) { throw new InvalidArgumentException(sprintf('No services found that match "%s".', $name)); } @@ -272,6 +282,24 @@ private function findProperServiceName(InputInterface $input, SymfonyStyle $io, return $io->choice('Select one of the following services to display its information', $matchingServices); } + private function findProperTagName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, string $tagName): string + { + if (\in_array($tagName, $builder->findTags(), true) || !$input->isInteractive()) { + return $tagName; + } + + $matchingTags = $this->findTagsContaining($builder, $tagName); + if (!$matchingTags) { + throw new InvalidArgumentException(sprintf('No tags found that match "%s".', $tagName)); + } + + if (1 === \count($matchingTags)) { + return $matchingTags[0]; + } + + return $io->choice('Select one of the following tags to display its information', $matchingTags); + } + private function findServiceIdsContaining(ContainerBuilder $builder, string $name, bool $showHidden): array { $serviceIds = $builder->getServiceIds(); @@ -291,6 +319,19 @@ private function findServiceIdsContaining(ContainerBuilder $builder, string $nam return $foundServiceIds ?: $foundServiceIdsIgnoringBackslashes; } + private function findTagsContaining(ContainerBuilder $builder, string $tagName): array + { + $tags = $builder->findTags(); + $foundTags = []; + foreach ($tags as $tag) { + if (str_contains($tag, $tagName)) { + $foundTags[] = $tag; + } + } + + return $foundTags; + } + /** * @internal */ diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php index 5e3e891ee845f..8100793faf3dc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php @@ -33,9 +33,6 @@ final class ContainerLintCommand extends Command { private ContainerBuilder $containerBuilder; - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -43,9 +40,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -92,7 +86,7 @@ private function getContainerBuilder(): ContainerBuilder $this->initializeBundles(); return $this->buildContainer(); - }, $kernel, \get_class($kernel)); + }, $kernel, $kernel::class); $container = $buildContainer(); $skippedIds = []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php index ecd507127c00c..8e5395c7a4c01 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php @@ -43,9 +43,6 @@ public function __construct(string $name = null, FileLinkFormatter $fileLinkForm parent::__construct($name); } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -68,9 +65,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -87,7 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.'); }); - if (empty($serviceIds)) { + if (!$serviceIds) { $errorIo->error(sprintf('No autowirable classes or interfaces found matching "%s"', $search)); return 1; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php index a8bb8303071db..41edb7505ab5c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php @@ -46,9 +46,6 @@ public function __construct(ContainerInterface $dispatchers) $this->dispatchers = $dispatchers; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -72,8 +69,6 @@ protected function configure() } /** - * {@inheritdoc} - * * @throws \LogicException */ protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index 7d2d6e9340304..a4c53beff3957 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -50,9 +50,6 @@ public function __construct(RouterInterface $router, FileLinkFormatter $fileLink $this->fileLinkFormatter = $fileLinkFormatter; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -73,8 +70,6 @@ protected function configure() } /** - * {@inheritdoc} - * * @throws InvalidArgumentException When route does not exist */ protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php index da78d510a6b4c..a654522d806f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php @@ -47,9 +47,6 @@ public function __construct(RouterInterface $router, iterable $expressionLanguag $this->expressionLanguageProviders = $expressionLanguageProviders; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -73,9 +70,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index c2cfae702863b..f255053db3d5b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -73,9 +73,6 @@ public function __construct(TranslatorInterface $translator, TranslationReaderIn $this->enabledLocales = $enabledLocales; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -121,9 +118,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -186,7 +180,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } // No defined or extracted messages - if (empty($allMessages) || null !== $domain && empty($allMessages[$domain])) { + if (!$allMessages || null !== $domain && empty($allMessages[$domain])) { $outputMessage = sprintf('No defined or extracted messages for locale "%s"', $locale); if (null !== $domain) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index e63be384b8620..426bd652ec05a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -75,9 +75,6 @@ public function __construct(TranslationWriterInterface $writer, TranslationReade $this->enabledLocales = $enabledLocales; } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -126,9 +123,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php index eb96e65470ebf..3daa2b9ddb71a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @@ -20,12 +20,14 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Dumper\GraphvizDumper; use Symfony\Component\Workflow\Dumper\MermaidDumper; use Symfony\Component\Workflow\Dumper\PlantUmlDumper; use Symfony\Component\Workflow\Dumper\StateMachineGraphvizDumper; use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\StateMachine; /** * @author Grégoire Pineau @@ -40,7 +42,9 @@ class WorkflowDumpCommand extends Command * * @var array */ - private array $workflows = []; + private array $definitions = []; + + private ServiceLocator $workflows; private const DUMP_FORMAT_OPTIONS = [ 'puml', @@ -48,16 +52,20 @@ class WorkflowDumpCommand extends Command 'dot', ]; - public function __construct(array $workflows) + public function __construct($workflows) { parent::__construct(); - $this->workflows = $workflows; + if ($workflows instanceof ServiceLocator) { + $this->workflows = $workflows; + } elseif (\is_array($workflows)) { + $this->definitions = $workflows; + trigger_deprecation('symfony/framework-bundle', '6.2', 'Passing an array of definitions in "%s()" is deprecated. Inject a ServiceLocator filled with all workflows instead.', __METHOD__); + } else { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be an array or a ServiceLocator, "%s" given.', __METHOD__, \gettype($workflows))); + } } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -79,24 +87,28 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $workflowName = $input->getArgument('name'); $workflow = null; - if (isset($this->workflows['workflow.'.$workflowName])) { - $workflow = $this->workflows['workflow.'.$workflowName]; + if (isset($this->workflows)) { + if (!$this->workflows->has($workflowName)) { + throw new InvalidArgumentException(sprintf('The workflow named "%s" cannot be found.', $workflowName)); + } + $workflow = $this->workflows->get($workflowName); + $type = $workflow instanceof StateMachine ? 'state_machine' : 'workflow'; + $definition = $workflow->getDefinition(); + } elseif (isset($this->definitions['workflow.'.$workflowName])) { + $definition = $this->definitions['workflow.'.$workflowName]; $type = 'workflow'; - } elseif (isset($this->workflows['state_machine.'.$workflowName])) { - $workflow = $this->workflows['state_machine.'.$workflowName]; + } elseif (isset($this->definitions['state_machine.'.$workflowName])) { + $definition = $this->definitions['state_machine.'.$workflowName]; $type = 'state_machine'; } - if (null === $workflow) { + if (null === $definition) { throw new InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $workflowName)); } @@ -129,7 +141,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'label' => $input->getOption('label'), ], ]; - $output->writeln($dumper->dump($workflow, $marking, $options)); + $output->writeln($dumper->dump($definition, $marking, $options)); return 0; } @@ -137,7 +149,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('name')) { - $suggestions->suggestValues(array_keys($this->workflows)); + if (isset($this->workflows)) { + $suggestions->suggestValues(array_keys($this->workflows->getProvidedServices())); + } else { + $suggestions->suggestValues(array_keys($this->definitions)); + } } if ($input->mustSuggestOptionValuesFor('dump-format')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php index e6d21f40b3dc1..0c33e2b8b9c84 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php @@ -43,9 +43,6 @@ public function __construct() parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } - /** - * {@inheritdoc} - */ protected function configure() { parent::configure(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php index 0686cde37ccba..42c1e795ccee6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php @@ -42,9 +42,6 @@ public function __construct() parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } - /** - * {@inheritdoc} - */ protected function configure() { parent::configure(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 8927eb1e07b1d..d73f323af7bbc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -52,9 +52,6 @@ public function getKernel(): KernelInterface return $this->kernel; } - /** - * {@inheritdoc} - */ public function reset() { if ($this->kernel->getContainer()->has('services_resetter')) { @@ -80,9 +77,6 @@ public function doRun(InputInterface $input, OutputInterface $output): int return parent::doRun($input, $output); } - /** - * {@inheritdoc} - */ protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int { if (!$command instanceof ListCommand) { @@ -104,9 +98,6 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI return $returnCode; } - /** - * {@inheritdoc} - */ public function find(string $name): Command { $this->registerCommands(); @@ -114,9 +105,6 @@ public function find(string $name): Command return parent::find($name); } - /** - * {@inheritdoc} - */ public function get(string $name): Command { $this->registerCommands(); @@ -130,9 +118,6 @@ public function get(string $name): Command return $command; } - /** - * {@inheritdoc} - */ public function all(string $namespace = null): array { $this->registerCommands(); @@ -140,9 +125,6 @@ public function all(string $namespace = null): array return parent::all($namespace); } - /** - * {@inheritdoc} - */ public function getLongVersion(): string { return parent::getLongVersion().sprintf(' (env: %s, debug: %s) #StandWithUkraine https://sf.to/ukraine', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php index 62a60f4d1a9b2..64c296e813671 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php @@ -15,9 +15,12 @@ use Symfony\Component\Console\Descriptor\DescriptorInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; +use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphEdge; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Routing\Route; @@ -35,55 +38,33 @@ abstract class Descriptor implements DescriptorInterface */ protected $output; - /** - * {@inheritdoc} - */ public function describe(OutputInterface $output, mixed $object, array $options = []) { $this->output = $output; - switch (true) { - case $object instanceof RouteCollection: - $this->describeRouteCollection($object, $options); - break; - case $object instanceof Route: - $this->describeRoute($object, $options); - break; - case $object instanceof ParameterBag: - $this->describeContainerParameters($object, $options); - break; - case $object instanceof ContainerBuilder && !empty($options['env-vars']): - $this->describeContainerEnvVars($this->getContainerEnvVars($object), $options); - break; - case $object instanceof ContainerBuilder && isset($options['group_by']) && 'tags' === $options['group_by']: - $this->describeContainerTags($object, $options); - break; - case $object instanceof ContainerBuilder && isset($options['id']): - $this->describeContainerService($this->resolveServiceDefinition($object, $options['id']), $options, $object); - break; - case $object instanceof ContainerBuilder && isset($options['parameter']): - $this->describeContainerParameter($object->resolveEnvPlaceholders($object->getParameter($options['parameter'])), $options); - break; - case $object instanceof ContainerBuilder && isset($options['deprecations']): - $this->describeContainerDeprecations($object, $options); - break; - case $object instanceof ContainerBuilder: - $this->describeContainerServices($object, $options); - break; - case $object instanceof Definition: - $this->describeContainerDefinition($object, $options); - break; - case $object instanceof Alias: - $this->describeContainerAlias($object, $options); - break; - case $object instanceof EventDispatcherInterface: - $this->describeEventDispatcherListeners($object, $options); - break; - case \is_callable($object): - $this->describeCallable($object, $options); - break; - default: - throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))); + if ($object instanceof ContainerBuilder) { + (new AnalyzeServiceReferencesPass(false, false))->process($object); + } + + match (true) { + $object instanceof RouteCollection => $this->describeRouteCollection($object, $options), + $object instanceof Route => $this->describeRoute($object, $options), + $object instanceof ParameterBag => $this->describeContainerParameters($object, $options), + $object instanceof ContainerBuilder && !empty($options['env-vars']) => $this->describeContainerEnvVars($this->getContainerEnvVars($object), $options), + $object instanceof ContainerBuilder && isset($options['group_by']) && 'tags' === $options['group_by'] => $this->describeContainerTags($object, $options), + $object instanceof ContainerBuilder && isset($options['id']) => $this->describeContainerService($this->resolveServiceDefinition($object, $options['id']), $options, $object), + $object instanceof ContainerBuilder && isset($options['parameter']) => $this->describeContainerParameter($object->resolveEnvPlaceholders($object->getParameter($options['parameter'])), $options), + $object instanceof ContainerBuilder && isset($options['deprecations']) => $this->describeContainerDeprecations($object, $options), + $object instanceof ContainerBuilder => $this->describeContainerServices($object, $options), + $object instanceof Definition => $this->describeContainerDefinition($object, $options), + $object instanceof Alias => $this->describeContainerAlias($object, $options), + $object instanceof EventDispatcherInterface => $this->describeEventDispatcherListeners($object, $options), + \is_callable($object) => $this->describeCallable($object, $options), + default => throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))), + }; + + if ($object instanceof ContainerBuilder) { + $object->getCompiler()->getServiceReferenceGraph()->clear(); } } @@ -125,7 +106,7 @@ abstract protected function describeContainerServices(ContainerBuilder $builder, abstract protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void; - abstract protected function describeContainerDefinition(Definition $definition, array $options = []); + abstract protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null); abstract protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null); @@ -150,7 +131,7 @@ protected function formatValue(mixed $value): string } if (\is_object($value)) { - return sprintf('object(%s)', \get_class($value)); + return sprintf('object(%s)', $value::class); } if (\is_string($value)) { @@ -327,7 +308,7 @@ private function getContainerEnvVars(ContainerBuilder $container): array $getDefaultParameter = function (string $name) { return parent::get($name); }; - $getDefaultParameter = $getDefaultParameter->bindTo($bag, \get_class($bag)); + $getDefaultParameter = $getDefaultParameter->bindTo($bag, $bag::class); $getEnvReflection = new \ReflectionMethod($container, 'getEnv'); @@ -358,4 +339,15 @@ private function getContainerEnvVars(ContainerBuilder $container): array return array_values($envs); } + + protected function getServiceEdges(ContainerBuilder $builder, string $serviceId): array + { + try { + return array_map(function (ServiceReferenceGraphEdge $edge) { + return $edge->getSourceNode()->getId(); + }, $builder->getCompiler()->getServiceReferenceGraph()->getNode($serviceId)->getInEdges()); + } catch (InvalidArgumentException $exception) { + return []; + } + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index 01394ff70179d..b47b37c7398fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -60,7 +60,7 @@ protected function describeContainerTags(ContainerBuilder $builder, array $optio foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { $data[$tag] = []; foreach ($definitions as $definition) { - $data[$tag][] = $this->getContainerDefinitionData($definition, true); + $data[$tag][] = $this->getContainerDefinitionData($definition, true, false, $builder, $options['id'] ?? null); } } @@ -76,9 +76,9 @@ protected function describeContainerService(object $service, array $options = [] if ($service instanceof Alias) { $this->describeContainerAlias($service, $options, $builder); } elseif ($service instanceof Definition) { - $this->writeData($this->getContainerDefinitionData($service, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']), $options); + $this->writeData($this->getContainerDefinitionData($service, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $builder, $options['id']), $options); } else { - $this->writeData(\get_class($service), $options); + $this->writeData($service::class, $options); } } @@ -106,18 +106,18 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o if ($service instanceof Alias) { $data['aliases'][$serviceId] = $this->getContainerAliasData($service); } elseif ($service instanceof Definition) { - $data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments); + $data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments, $builder, $serviceId); } else { - $data['services'][$serviceId] = \get_class($service); + $data['services'][$serviceId] = $service::class; } } $this->writeData($data, $options); } - protected function describeContainerDefinition(Definition $definition, array $options = []) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null) { - $this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']), $options); + $this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $builder, $options['id'] ?? null), $options); } protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) @@ -129,7 +129,7 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con } $this->writeData( - [$this->getContainerAliasData($alias), $this->getContainerDefinitionData($builder->getDefinition((string) $alias), isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'])], + [$this->getContainerAliasData($alias), $this->getContainerDefinitionData($builder->getDefinition((string) $alias), isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $builder, (string) $alias)], array_merge($options, ['id' => (string) $alias]) ); } @@ -204,7 +204,7 @@ protected function getRouteData(Route $route): array 'hostRegex' => '' !== $route->getHost() ? $route->compile()->getHostRegex() : '', 'scheme' => $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY', 'method' => $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY', - 'class' => \get_class($route), + 'class' => $route::class, 'defaults' => $route->getDefaults(), 'requirements' => $route->getRequirements() ?: 'NO CUSTOM', 'options' => $route->getOptions(), @@ -217,7 +217,7 @@ protected function getRouteData(Route $route): array return $data; } - private function getContainerDefinitionData(Definition $definition, bool $omitTags = false, bool $showArguments = false): array + private function getContainerDefinitionData(Definition $definition, bool $omitTags = false, bool $showArguments = false, ContainerBuilder $builder = null, string $id = null): array { $data = [ 'class' => (string) $definition->getClass(), @@ -230,12 +230,19 @@ private function getContainerDefinitionData(Definition $definition, bool $omitTa 'autoconfigure' => $definition->isAutoconfigured(), ]; + if ($definition->isDeprecated()) { + $data['deprecated'] = true; + $data['deprecation_message'] = $definition->getDeprecation($id)['message']; + } else { + $data['deprecated'] = false; + } + if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) { $data['description'] = $classDescription; } if ($showArguments) { - $data['arguments'] = $this->describeValue($definition->getArguments(), $omitTags, $showArguments); + $data['arguments'] = $this->describeValue($definition->getArguments(), $omitTags, $showArguments, $builder, $id); } $data['file'] = $definition->getFile(); @@ -272,6 +279,8 @@ private function getContainerDefinitionData(Definition $definition, bool $omitTa } } + $data['usages'] = null !== $builder && null !== $id ? $this->getServiceEdges($builder, $id) : []; + return $data; } @@ -373,7 +382,7 @@ private function getCallableData(mixed $callable): array if (method_exists($callable, '__invoke')) { $data['type'] = 'object'; - $data['name'] = \get_class($callable); + $data['name'] = $callable::class; return $data; } @@ -381,12 +390,12 @@ private function getCallableData(mixed $callable): array throw new \InvalidArgumentException('Callable is not describable.'); } - private function describeValue(mixed $value, bool $omitTags, bool $showArguments): mixed + private function describeValue($value, bool $omitTags, bool $showArguments, ContainerBuilder $builder = null, string $id = null): mixed { if (\is_array($value)) { $data = []; foreach ($value as $k => $v) { - $data[$k] = $this->describeValue($v, $omitTags, $showArguments); + $data[$k] = $this->describeValue($v, $omitTags, $showArguments, $builder, $id); } return $data; @@ -408,11 +417,11 @@ private function describeValue(mixed $value, bool $omitTags, bool $showArguments } if ($value instanceof ArgumentInterface) { - return $this->describeValue($value->getValues(), $omitTags, $showArguments); + return $this->describeValue($value->getValues(), $omitTags, $showArguments, $builder, $id); } if ($value instanceof Definition) { - return $this->getContainerDefinitionData($value, $omitTags, $showArguments); + return $this->getContainerDefinitionData($value, $omitTags, $showArguments, $builder, $id); } return $value; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 810104e0e5c9d..555911a761506 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -51,7 +51,7 @@ protected function describeRoute(Route $route, array $options = []) ."\n".'- Host Regex: '.('' !== $route->getHost() ? $route->compile()->getHostRegex() : '') ."\n".'- Scheme: '.($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY') ."\n".'- Method: '.($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY') - ."\n".'- Class: '.\get_class($route) + ."\n".'- Class: '.$route::class ."\n".'- Defaults: '.$this->formatRouterConfig($route->getDefaults()) ."\n".'- Requirements: '.($route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM') ."\n".'- Options: '.$this->formatRouterConfig($route->getOptions()); @@ -83,7 +83,7 @@ protected function describeContainerTags(ContainerBuilder $builder, array $optio $this->write("\n\n".$tag."\n".str_repeat('-', \strlen($tag))); foreach ($definitions as $serviceId => $definition) { $this->write("\n\n"); - $this->describeContainerDefinition($definition, ['omit_tags' => true, 'id' => $serviceId]); + $this->describeContainerDefinition($definition, ['omit_tags' => true, 'id' => $serviceId], $builder); } } } @@ -99,9 +99,9 @@ protected function describeContainerService(object $service, array $options = [] if ($service instanceof Alias) { $this->describeContainerAlias($service, $childOptions, $builder); } elseif ($service instanceof Definition) { - $this->describeContainerDefinition($service, $childOptions); + $this->describeContainerDefinition($service, $childOptions, $builder); } else { - $this->write(sprintf('**`%s`:** `%s`', $options['id'], \get_class($service))); + $this->write(sprintf('**`%s`:** `%s`', $options['id'], $service::class)); } } @@ -172,7 +172,7 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $this->write("\n\nDefinitions\n-----------\n"); foreach ($services['definitions'] as $id => $service) { $this->write("\n"); - $this->describeContainerDefinition($service, ['id' => $id, 'show_arguments' => $showArguments]); + $this->describeContainerDefinition($service, ['id' => $id, 'show_arguments' => $showArguments], $builder); } } @@ -188,12 +188,12 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $this->write("\n\nServices\n--------\n"); foreach ($services['services'] as $id => $service) { $this->write("\n"); - $this->write(sprintf('- `%s`: `%s`', $id, \get_class($service))); + $this->write(sprintf('- `%s`: `%s`', $id, $service::class)); } } } - protected function describeContainerDefinition(Definition $definition, array $options = []) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null) { $output = ''; @@ -211,6 +211,13 @@ protected function describeContainerDefinition(Definition $definition, array $op ."\n".'- Autoconfigured: '.($definition->isAutoconfigured() ? 'yes' : 'no') ; + if ($definition->isDeprecated()) { + $output .= "\n".'- Deprecated: yes'; + $output .= "\n".'- Deprecation message: '.$definition->getDeprecation($options['id'])['message']; + } else { + $output .= "\n".'- Deprecated: no'; + } + if (isset($options['show_arguments']) && $options['show_arguments']) { $output .= "\n".'- Arguments: '.($definition->getArguments() ? 'yes' : 'no'); } @@ -250,6 +257,9 @@ protected function describeContainerDefinition(Definition $definition, array $op } } + $inEdges = null !== $builder && isset($options['id']) ? $this->getServiceEdges($builder, $options['id']) : []; + $output .= "\n".'- Usages: '.($inEdges ? implode(', ', $inEdges) : 'none'); + $this->write(isset($options['id']) ? sprintf("### %s\n\n%s\n", $options['id'], $output) : $output); } @@ -271,7 +281,7 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con } $this->write("\n"); - $this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias])); + $this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias]), $builder); } protected function describeContainerParameter(mixed $parameter, array $options = []) @@ -389,7 +399,7 @@ protected function describeCallable(mixed $callable, array $options = []) if (method_exists($callable, '__invoke')) { $string .= "\n- Type: `object`"; - $string .= "\n".sprintf('- Name: `%s`', \get_class($callable)); + $string .= "\n".sprintf('- Name: `%s`', $callable::class); return $this->write($string."\n"); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 1b31fe91af45b..1fb75fbbf6c35 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -98,7 +98,7 @@ protected function describeRoute(Route $route, array $options = []) ['Scheme', $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY'], ['Method', $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY'], ['Requirements', $route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM'], - ['Class', \get_class($route)], + ['Class', $route::class], ['Defaults', $this->formatRouterConfig($defaults)], ['Options', $this->formatRouterConfig($route->getOptions())], ]; @@ -150,13 +150,13 @@ protected function describeContainerService(object $service, array $options = [] if ($service instanceof Alias) { $this->describeContainerAlias($service, $options, $builder); } elseif ($service instanceof Definition) { - $this->describeContainerDefinition($service, $options); + $this->describeContainerDefinition($service, $options, $builder); } else { $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); $options['output']->table( ['Service ID', 'Class'], [ - [$options['id'] ?? '-', \get_class($service)], + [$options['id'] ?? '-', $service::class], ] ); } @@ -244,14 +244,14 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $alias = $definition; $tableRows[] = array_merge([$styledServiceId, sprintf('alias for "%s"', $alias)], $tagsCount ? array_fill(0, $tagsCount, '') : []); } else { - $tableRows[] = array_merge([$styledServiceId, \get_class($definition)], $tagsCount ? array_fill(0, $tagsCount, '') : []); + $tableRows[] = array_merge([$styledServiceId, $definition::class], $tagsCount ? array_fill(0, $tagsCount, '') : []); } } $options['output']->table($tableHeaders, $tableRows); } - protected function describeContainerDefinition(Definition $definition, array $options = []) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null) { if (isset($options['id'])) { $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); @@ -360,6 +360,9 @@ protected function describeContainerDefinition(Definition $definition, array $op $tableRows[] = ['Arguments', implode("\n", $argumentsInformation)]; } + $inEdges = null !== $builder && isset($options['id']) ? $this->getServiceEdges($builder, $options['id']) : []; + $tableRows[] = ['Usages', $inEdges ? implode(', ', $inEdges) : 'none']; + $options['output']->table($tableHeaders, $tableRows); } @@ -401,7 +404,7 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con return; } - $this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias])); + $this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias]), $builder); } protected function describeContainerParameter(mixed $parameter, array $options = []) @@ -527,7 +530,7 @@ private function renderEventListenerTable(EventDispatcherInterface $eventDispatc private function formatRouterConfig(array $config): string { - if (empty($config)) { + if (!$config) { return 'NONE'; } @@ -622,7 +625,7 @@ private function formatCallable(mixed $callable): string } if (method_exists($callable, '__invoke')) { - return sprintf('%s::__invoke()', \get_class($callable)); + return sprintf('%s::__invoke()', $callable::class); } throw new \InvalidArgumentException('Callable is not describable.'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index d7187614e8d2c..955a278d86ebb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -64,12 +64,12 @@ protected function describeContainerService(object $service, array $options = [] protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { - $this->writeDocument($this->getContainerServicesDocument($builder, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null)); + $this->writeDocument($this->getContainerServicesDocument($builder, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null, $options['id'] ?? null)); } - protected function describeContainerDefinition(Definition $definition, array $options = []) + protected function describeContainerDefinition(Definition $definition, array $options = [], ContainerBuilder $builder = null) { - $this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'])); + $this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'], $builder)); } protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) @@ -83,7 +83,7 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con return; } - $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $alias), (string) $alias)->childNodes->item(0), true)); + $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $alias), (string) $alias, false, false, $builder)->childNodes->item(0), true)); $this->writeDocument($dom); } @@ -164,7 +164,7 @@ private function getRouteDocument(Route $route, string $name = null): \DOMDocume $routeXML->setAttribute('name', $name); } - $routeXML->setAttribute('class', \get_class($route)); + $routeXML->setAttribute('class', $route::class); $routeXML->appendChild($pathXML = $dom->createElement('path')); $pathXML->setAttribute('regex', $route->compile()->getRegex()); @@ -247,7 +247,7 @@ private function getContainerTagsDocument(ContainerBuilder $builder, bool $showH $tagXML->setAttribute('name', $tag); foreach ($definitions as $serviceId => $definition) { - $definitionXML = $this->getContainerDefinitionDocument($definition, $serviceId, true); + $definitionXML = $this->getContainerDefinitionDocument($definition, $serviceId, true, false, $builder); $tagXML->appendChild($dom->importNode($definitionXML->childNodes->item(0), true)); } } @@ -262,20 +262,20 @@ private function getContainerServiceDocument(object $service, string $id, Contai if ($service instanceof Alias) { $dom->appendChild($dom->importNode($this->getContainerAliasDocument($service, $id)->childNodes->item(0), true)); if ($builder) { - $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $service), (string) $service, false, $showArguments)->childNodes->item(0), true)); + $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $service), (string) $service, false, $showArguments, $builder)->childNodes->item(0), true)); } } elseif ($service instanceof Definition) { - $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($service, $id, false, $showArguments)->childNodes->item(0), true)); + $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($service, $id, false, $showArguments, $builder)->childNodes->item(0), true)); } else { $dom->appendChild($serviceXML = $dom->createElement('service')); $serviceXML->setAttribute('id', $id); - $serviceXML->setAttribute('class', \get_class($service)); + $serviceXML->setAttribute('class', $service::class); } return $dom; } - private function getContainerServicesDocument(ContainerBuilder $builder, string $tag = null, bool $showHidden = false, bool $showArguments = false, callable $filter = null): \DOMDocument + private function getContainerServicesDocument(ContainerBuilder $builder, string $tag = null, bool $showHidden = false, bool $showArguments = false, callable $filter = null, string $id = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($containerXML = $dom->createElement('container')); @@ -301,7 +301,7 @@ private function getContainerServicesDocument(ContainerBuilder $builder, string return $dom; } - private function getContainerDefinitionDocument(Definition $definition, string $id = null, bool $omitTags = false, bool $showArguments = false): \DOMDocument + private function getContainerDefinitionDocument(Definition $definition, string $id = null, bool $omitTags = false, bool $showArguments = false, ContainerBuilder $builder = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($serviceXML = $dom->createElement('definition')); @@ -341,6 +341,12 @@ private function getContainerDefinitionDocument(Definition $definition, string $ $serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false'); $serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false'); $serviceXML->setAttribute('autoconfigured', $definition->isAutoconfigured() ? 'true' : 'false'); + if ($definition->isDeprecated()) { + $serviceXML->setAttribute('deprecated', 'true'); + $serviceXML->setAttribute('deprecation_message', $definition->getDeprecation($id)['message']); + } else { + $serviceXML->setAttribute('deprecated', 'false'); + } $serviceXML->setAttribute('file', $definition->getFile() ?? ''); $calls = $definition->getMethodCalls(); @@ -356,7 +362,7 @@ private function getContainerDefinitionDocument(Definition $definition, string $ } if ($showArguments) { - foreach ($this->getArgumentNodes($definition->getArguments(), $dom) as $node) { + foreach ($this->getArgumentNodes($definition->getArguments(), $dom, $builder) as $node) { $serviceXML->appendChild($node); } } @@ -378,13 +384,24 @@ private function getContainerDefinitionDocument(Definition $definition, string $ } } + if (null !== $builder && null !== $id) { + $edges = $this->getServiceEdges($builder, $id); + if ($edges) { + $serviceXML->appendChild($usagesXML = $dom->createElement('usages')); + foreach ($edges as $edge) { + $usagesXML->appendChild($usageXML = $dom->createElement('usage')); + $usageXML->appendChild(new \DOMText($edge)); + } + } + } + return $dom; } /** * @return \DOMNode[] */ - private function getArgumentNodes(array $arguments, \DOMDocument $dom): array + private function getArgumentNodes(array $arguments, \DOMDocument $dom, ContainerBuilder $builder = null): array { $nodes = []; @@ -405,18 +422,18 @@ private function getArgumentNodes(array $arguments, \DOMDocument $dom): array } elseif ($argument instanceof IteratorArgument || $argument instanceof ServiceLocatorArgument) { $argumentXML->setAttribute('type', $argument instanceof IteratorArgument ? 'iterator' : 'service_locator'); - foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) { + foreach ($this->getArgumentNodes($argument->getValues(), $dom, $builder) as $childArgumentXML) { $argumentXML->appendChild($childArgumentXML); } } elseif ($argument instanceof Definition) { - $argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, false, true)->childNodes->item(0), true)); + $argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, false, true, $builder)->childNodes->item(0), true)); } elseif ($argument instanceof AbstractArgument) { $argumentXML->setAttribute('type', 'abstract'); $argumentXML->appendChild(new \DOMText($argument->getText())); } elseif (\is_array($argument)) { $argumentXML->setAttribute('type', 'collection'); - foreach ($this->getArgumentNodes($argument, $dom) as $childArgumentXML) { + foreach ($this->getArgumentNodes($argument, $dom, $builder) as $childArgumentXML) { $argumentXML->appendChild($childArgumentXML); } } elseif ($argument instanceof \UnitEnum) { @@ -560,7 +577,7 @@ private function getCallableDocument(mixed $callable): \DOMDocument if (method_exists($callable, '__invoke')) { $callableXML->setAttribute('type', 'object'); - $callableXML->setAttribute('name', \get_class($callable)); + $callableXML->setAttribute('name', $callable::class); return $dom; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index 3be4b0f517411..da14b738b3b97 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -19,7 +19,6 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; -use Symfony\Component\Form\FormView; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use Symfony\Component\HttpFoundation\JsonResponse; @@ -28,6 +27,7 @@ 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; @@ -124,6 +124,8 @@ protected function forward(string $controller, array $path = [], array $query = /** * Returns a RedirectResponse to the given URL. + * + * @param int $status The HTTP status code (302 "Found" by default) */ protected function redirect(string $url, int $status = 302): RedirectResponse { @@ -132,6 +134,8 @@ protected function redirect(string $url, int $status = 302): RedirectResponse /** * Returns a RedirectResponse to the given route with the given parameters. + * + * @param int $status The HTTP status code (302 "Found" by default) */ protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse { @@ -140,6 +144,8 @@ protected function redirectToRoute(string $route, array $parameters = [], int $s /** * Returns a JsonResponse that uses the serializer component if enabled, or json_encode. + * + * @param int $status The HTTP status code (200 "OK" by default) */ protected function json(mixed $data, int $status = 200, array $headers = [], array $context = []): JsonResponse { @@ -173,10 +179,16 @@ protected function file(\SplFileInfo|string $file, string $fileName = null, stri protected function addFlash(string $type, mixed $message): void { try { - $this->container->get('request_stack')->getSession()->getFlashBag()->add($type, $message); + $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) { + trigger_deprecation('symfony/framework-bundle', '6.2', 'Calling "addFlash()" method when the session does not implement %s is deprecated.', FlashBagAwareSessionInterface::class); + } + + $session->getFlashBag()->add($type, $message); } /** @@ -212,6 +224,8 @@ protected function denyAccessUnlessGranted(mixed $attribute, mixed $subject = nu /** * Returns a rendered view. + * + * Forms found in parameters are auto-cast to form views. */ protected function renderView(string $view, array $parameters = []): string { @@ -219,11 +233,20 @@ protected function renderView(string $view, array $parameters = []): string throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); } + foreach ($parameters as $k => $v) { + if ($v instanceof FormInterface) { + $parameters[$k] = $v->createView(); + } + } + return $this->container->get('twig')->render($view, $parameters); } /** * 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. */ protected function render(string $view, array $parameters = [], Response $response = null): Response { @@ -233,6 +256,15 @@ protected function render(string $view, array $parameters = [], Response $respon $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; @@ -242,28 +274,12 @@ protected function render(string $view, array $parameters = [], Response $respon * Renders a view and sets the appropriate status code when a form is listed in parameters. * * If an invalid form is found in the list of parameters, a 422 status code is returned. + * + * @deprecated since Symfony 6.2, use render() instead */ protected function renderForm(string $view, array $parameters = [], Response $response = null): Response { - if (null === $response) { - $response = new Response(); - } - - foreach ($parameters as $k => $v) { - if ($v instanceof FormView) { - throw new \LogicException(sprintf('Passing a FormView to "%s::renderForm()" is not supported, pass directly the form instead for parameter "%s".', get_debug_type($this), $k)); - } - - if (!$v instanceof FormInterface) { - continue; - } - - $parameters[$k] = $v->createView(); - - if (200 === $response->getStatusCode() && $v->isSubmitted() && !$v->isValid()) { - $response->setStatusCode(422); - } - } + trigger_deprecation('symfony/framework-bundle', '6.2', 'The "%s::renderForm()" method is deprecated, use "render()" instead.', get_debug_type($this)); return $this->render($view, $parameters, $response); } @@ -364,7 +380,7 @@ protected function getUser(): ?UserInterface * @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 */ - protected function isCsrfTokenValid(string $id, ?string $token): bool + protected 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".'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php index 0539c1ee734ca..2debdbb1629bb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php @@ -21,9 +21,6 @@ */ class ControllerResolver extends ContainerControllerResolver { - /** - * {@inheritdoc} - */ protected function instantiateController(string $class): object { $controller = parent::instantiateController($class); diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php index 4aacf24fc05a7..3d754bb7d0b1e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php @@ -38,7 +38,7 @@ public function __construct(Environment $twig = null) * @param int|null $sharedAge Max age for shared (proxy) caching * @param bool|null $private Whether or not caching should apply for client caches only * @param array $context The context (arguments) of the template - * @param int $statusCode The HTTP status code to return with the response. Defaults to 200 + * @param int $statusCode The HTTP status code to return with the response (200 "OK" by default) */ public function templateAction(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response { @@ -65,6 +65,9 @@ public function templateAction(string $template, int $maxAge = null, int $shared return $response; } + /** + * @param int $statusCode The HTTP status code (200 "OK" by default) + */ public function __invoke(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response { return $this->templateAction($template, $maxAge, $sharedAge, $private, $context, $statusCode); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php index d7db6ebf050a4..fbbf53d3431f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php @@ -20,9 +20,6 @@ */ class AddAnnotationsCachedReaderPass implements CompilerPassInterface { - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { // "annotations.cached_reader" is wired late so that any passes using diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php index a1bf0a80eadb0..f1e64ca6c6a0b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\Log\Logger; class AddDebugLogProcessorPass implements CompilerPassInterface { @@ -22,22 +23,38 @@ public function process(ContainerBuilder $container) if (!$container->hasDefinition('profiler')) { return; } - if (!$container->hasDefinition('monolog.logger_prototype')) { + + if ($container->hasDefinition('monolog.logger_prototype') && $container->hasDefinition('debug.log_processor')) { + $container->getDefinition('monolog.logger_prototype') + ->setConfigurator([__CLASS__, 'configureMonologLogger']) + ->addMethodCall('pushProcessor', [new Reference('debug.log_processor')]) + ; + return; } - if (!$container->hasDefinition('debug.log_processor')) { + + if (!$container->hasDefinition('logger')) { return; } - $definition = $container->getDefinition('monolog.logger_prototype'); - $definition->setConfigurator([__CLASS__, 'configureLogger']); - $definition->addMethodCall('pushProcessor', [new Reference('debug.log_processor')]); + $loggerDefinition = $container->getDefinition('logger'); + + if (Logger::class === $loggerDefinition->getClass()) { + $loggerDefinition->setConfigurator([__CLASS__, 'configureHttpKernelLogger']); + } } - public static function configureLogger(mixed $logger) + public static function configureMonologLogger(mixed $logger) { - if (\is_object($logger) && method_exists($logger, 'removeDebugLogger') && \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && \is_object($logger) && method_exists($logger, 'removeDebugLogger')) { $logger->removeDebugLogger(); } } + + public static function configureHttpKernelLogger(Logger $logger) + { + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && method_exists($logger, 'enableDebug')) { + $logger->enableDebug(); + } + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php index 3e2f2768edc1a..f7a6bcdbd710a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php @@ -22,9 +22,6 @@ */ class AddExpressionLanguageProvidersPass implements CompilerPassInterface { - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { // routing diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 32b578ee7b64a..f127f32b49952 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -34,6 +34,7 @@ class UnusedTagsPass implements CompilerPassInterface 'container.do_not_inline', 'container.env_var_loader', 'container.env_var_processor', + 'container.excluded', 'container.hot_path', 'container.no_preload', 'container.preload', @@ -88,6 +89,7 @@ class UnusedTagsPass implements CompilerPassInterface 'texter.transport_factory', 'translation.dumper', 'translation.extractor', + 'translation.extractor.visitor', 'translation.loader', 'translation.provider_factory', 'twig.extension', @@ -96,6 +98,7 @@ class UnusedTagsPass implements CompilerPassInterface 'validator.auto_mapper', 'validator.constraint_validator', 'validator.initializer', + 'workflow', ]; public function process(ContainerBuilder $container) @@ -122,7 +125,7 @@ public function process(ContainerBuilder $container) $services = array_keys($container->findTaggedServiceIds($tag)); $message = sprintf('Tag "%s" was defined on service(s) "%s", but was never used.', $tag, implode('", "', $services)); - if (!empty($candidates)) { + if ($candidates) { $message .= sprintf(' Did you mean "%s"?', implode('", "', $candidates)); } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php index ad62e19384976..d74fa11b2ae6b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php @@ -21,9 +21,6 @@ */ class WorkflowGuardListenerPass implements CompilerPassInterface { - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { if (!$container->hasParameter('workflow.has_guard_listeners')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 63b8695b8edd5..4ac2a9dbedf38 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -133,6 +133,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('error_controller') ->defaultValue('error_controller') ->end() + ->booleanNode('handle_all_throwables')->info('HttpKernel will handle all kinds of \Throwable')->end() ->end() ; @@ -236,8 +237,9 @@ private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableI ->scalarNode('field_name')->defaultValue('_token')->end() ->end() ->end() - // to be deprecated in Symfony 6.1 - ->booleanNode('legacy_error_messages')->end() + ->booleanNode('legacy_error_messages') + ->setDeprecated('symfony/framework-bundle', '6.2') + ->end() ->end() ->end() ->end() @@ -267,6 +269,7 @@ private function addHttpCacheSection(ArrayNodeDefinition $rootNode) ->booleanNode('allow_revalidate')->end() ->integerNode('stale_while_revalidate')->end() ->integerNode('stale_if_error')->end() + ->booleanNode('terminate_on_cache_hit')->end() ->end() ->end() ->end() @@ -349,7 +352,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) $workflows = []; } - if (1 === \count($workflows) && isset($workflows['workflows']) && !array_is_list($workflows['workflows']) && !empty(array_diff(array_keys($workflows['workflows']), ['audit_trail', 'type', 'marking_store', 'supports', 'support_strategy', 'initial_marking', 'places', 'transitions']))) { + if (1 === \count($workflows) && isset($workflows['workflows']) && !array_is_list($workflows['workflows']) && array_diff(array_keys($workflows['workflows']), ['audit_trail', 'type', 'marking_store', 'supports', 'support_strategy', 'initial_marking', 'places', 'transitions'])) { $workflows = $workflows['workflows']; } @@ -605,6 +608,7 @@ private function addRouterSection(ArrayNodeDefinition $rootNode) ->children() ->scalarNode('resource')->isRequired()->end() ->scalarNode('type')->end() + ->scalarNode('cache_dir')->defaultValue('%kernel.cache_dir%')->end() ->scalarNode('default_uri') ->info('The default URI used to generate URLs in a non-HTTP context') ->defaultNull() @@ -1495,6 +1499,10 @@ function ($a) { ->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end() ->end() ->end() + ->scalarNode('rate_limiter') + ->defaultNull() + ->info('Rate limiter name to use when processing messages') + ->end() ->end() ->end() ->end() @@ -1513,15 +1521,36 @@ function ($a) { ->end() ->scalarNode('default_bus')->defaultNull()->end() ->arrayNode('buses') - ->defaultValue(['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]]) + ->defaultValue(['messenger.bus.default' => ['default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => []]]) ->normalizeKeys(false) ->useAttributeAsKey('name') ->arrayPrototype() ->addDefaultsIfNotSet() ->children() - ->enumNode('default_middleware') - ->values([true, false, 'allow_no_handlers']) - ->defaultTrue() + ->arrayNode('default_middleware') + ->beforeNormalization() + ->ifTrue(function ($defaultMiddleware) { return \is_string($defaultMiddleware) || \is_bool($defaultMiddleware); }) + ->then(function ($defaultMiddleware): array { + if (\is_string($defaultMiddleware) && 'allow_no_handlers' === $defaultMiddleware) { + return [ + 'enabled' => true, + 'allow_no_handlers' => true, + 'allow_no_senders' => true, + ]; + } + + return [ + 'enabled' => $defaultMiddleware, + 'allow_no_handlers' => false, + 'allow_no_senders' => true, + ]; + }) + ->end() + ->canBeDisabled() + ->children() + ->booleanNode('allow_no_handlers')->defaultFalse()->end() + ->booleanNode('allow_no_senders')->defaultTrue()->end() + ->end() ->end() ->arrayNode('middleware') ->performNoDeepMerging() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 1335e0a80f7c2..982210c5fcc4b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -17,16 +17,15 @@ use Http\Client\HttpClient; use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\Types\ContextFactory; +use PhpParser\Parser; use PHPStan\PhpDocParser\Parser\PhpDocParser; use Psr\Cache\CacheItemPoolInterface; use Psr\Container\ContainerInterface as PsrContainerInterface; -use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; use Psr\Http\Client\ClientInterface; use Psr\Log\LoggerAwareInterface; use Symfony\Bridge\Monolog\Processor\DebugProcessor; use Symfony\Bridge\Twig\Extension\CsrfExtension; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader; use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface; use Symfony\Bundle\FullStack; use Symfony\Bundle\MercureBundle\MercureBundle; @@ -40,6 +39,7 @@ use Symfony\Component\Cache\Marshaller\DefaultMarshaller; use Symfony\Component\Cache\Marshaller\MarshallerInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Clock\ClockInterface; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderInterface; @@ -87,9 +87,11 @@ use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\UidValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\LockInterface; use Symfony\Component\Lock\PersistingStoreInterface; use Symfony\Component\Lock\Store\StoreFactory; use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; @@ -101,6 +103,8 @@ use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory; +use Symfony\Component\Mailer\Command\MailerTestCommand; +use Symfony\Component\Mailer\EventListener\MessengerTransportListener; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mercure\HubRegistry; use Symfony\Component\Messenger\Attribute\AsMessageHandler; @@ -108,12 +112,12 @@ use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory; +use Symfony\Component\Messenger\Command\StatsCommand; use Symfony\Component\Messenger\Handler\BatchHandlerInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Middleware\RouterContextMiddleware; -use Symfony\Component\Messenger\Stamp\SerializedMessageStamp; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use Symfony\Component\Messenger\Transport\TransportFactoryInterface; use Symfony\Component\Messenger\Transport\TransportInterface; @@ -122,7 +126,9 @@ use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; +use Symfony\Component\Notifier\Bridge\Chatwork\ChatworkTransportFactory; use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; +use Symfony\Component\Notifier\Bridge\ContactEveryone\ContactEveryoneTransportFactory; use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; use Symfony\Component\Notifier\Bridge\Engagespot\EngagespotTransportFactory; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; @@ -160,6 +166,7 @@ use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; +use Symfony\Component\Notifier\Bridge\SmsFactor\SmsFactorTransportFactory; use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; @@ -167,6 +174,7 @@ use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; +use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use Symfony\Component\Notifier\ChatterInterface; use Symfony\Component\Notifier\Notifier; @@ -187,10 +195,9 @@ use Symfony\Component\RateLimiter\LimiterInterface; use Symfony\Component\RateLimiter\RateLimiterFactory; use Symfony\Component\RateLimiter\Storage\CacheStorage; -use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; -use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Routing\Loader\Psr4DirectoryLoader; +use Symfony\Component\Security\Core\AuthenticationEvents; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Semaphore\PersistingStoreInterface as SemaphoreStoreInterface; use Symfony\Component\Semaphore\Semaphore; @@ -211,11 +218,13 @@ use Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory; use Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory; use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand; +use Symfony\Component\Translation\Extractor\PhpAstExtractor; use Symfony\Component\Translation\LocaleSwitcher; use Symfony\Component\Translation\PseudoLocalizationTranslator; use Symfony\Component\Translation\Translator; use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Uid\UuidV4; +use Symfony\Component\Validator\Constraints\WhenValidator; use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\ObjectInitializerInterface; @@ -240,18 +249,7 @@ */ class FrameworkExtension extends Extension { - private bool $formConfigEnabled = false; - private bool $translationConfigEnabled = false; - private bool $sessionConfigEnabled = false; - private bool $annotationsConfigEnabled = false; - private bool $validatorConfigEnabled = false; - private bool $messengerConfigEnabled = false; - private bool $mailerConfigEnabled = false; - private bool $httpClientConfigEnabled = false; - private bool $notifierConfigEnabled = false; - private bool $serializerConfigEnabled = false; - private bool $propertyAccessConfigEnabled = false; - private static bool $lockConfigEnabled = false; + private array $configsEnabled = []; /** * Responds to the app.config configuration parameter. @@ -276,8 +274,9 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('fragment_renderer.php'); $loader->load('error_renderer.php'); - if (ContainerBuilder::willBeAvailable('psr/event-dispatcher', PsrEventDispatcherInterface::class, ['symfony/framework-bundle'])) { - $container->setAlias(PsrEventDispatcherInterface::class, 'event_dispatcher'); + if (!ContainerBuilder::willBeAvailable('symfony/clock', ClockInterface::class, ['symfony/framework-bundle'])) { + $container->removeDefinition('clock'); + $container->removeAlias(ClockInterface::class); } $container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class); @@ -303,14 +302,17 @@ public function load(array $configs, ContainerBuilder $container) $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); - $this->annotationsConfigEnabled = $this->isConfigEnabled($container, $config['annotations']); - $this->translationConfigEnabled = $this->isConfigEnabled($container, $config['translator']); + // warmup config enabled + $this->readConfigEnabled('annotations', $container, $config['annotations']); + $this->readConfigEnabled('translator', $container, $config['translator']); + $this->readConfigEnabled('property_access', $container, $config['property_access']); + $this->readConfigEnabled('profiler', $container, $config['profiler']); // A translator must always be registered (as support is included by // default in the Form and Validator component). If disabled, an identity // translator will be used and everything will still work as expected. - if ($this->isConfigEnabled($container, $config['translator']) || $this->isConfigEnabled($container, $config['form']) || $this->isConfigEnabled($container, $config['validation'])) { - if (!class_exists(Translator::class) && $this->isConfigEnabled($container, $config['translator'])) { + if ($this->readConfigEnabled('translator', $container, $config['translator']) || $this->readConfigEnabled('form', $container, $config['form']) || $this->readConfigEnabled('validation', $container, $config['validation'])) { + if (!class_exists(Translator::class) && $this->readConfigEnabled('translator', $container, $config['translator'])) { throw new LogicException('Translation support cannot be enabled as the Translation component is not installed. Try running "composer require symfony/translation".'); } @@ -321,6 +323,7 @@ public function load(array $configs, ContainerBuilder $container) $container->getDefinition('locale_listener')->replaceArgument(3, $config['set_locale_from_accept_language']); $container->getDefinition('response_listener')->replaceArgument(1, $config['set_content_language_from_locale']); + $container->getDefinition('http_kernel')->replaceArgument(4, $config['handle_all_throwables'] ?? false); // If the slugger is used but the String component is not available, we should throw an error if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'])) { @@ -365,11 +368,11 @@ public function load(array $configs, ContainerBuilder $container) } } - if ($this->isConfigEnabled($container, $config['request'])) { + if ($this->readConfigEnabled('request', $container, $config['request'])) { $this->registerRequestConfiguration($config['request'], $container, $loader); } - if ($this->isConfigEnabled($container, $config['assets'])) { + if ($this->readConfigEnabled('assets', $container, $config['assets'])) { if (!class_exists(\Symfony\Component\Asset\Package::class)) { throw new LogicException('Asset support cannot be enabled as the Asset component is not installed. Try running "composer require symfony/asset".'); } @@ -377,15 +380,19 @@ public function load(array $configs, ContainerBuilder $container) $this->registerAssetsConfiguration($config['assets'], $container, $loader); } - if ($this->httpClientConfigEnabled = $this->isConfigEnabled($container, $config['http_client'])) { - $this->registerHttpClientConfiguration($config['http_client'], $container, $loader, $config['profiler']); + if ($this->readConfigEnabled('http_client', $container, $config['http_client'])) { + $this->registerHttpClientConfiguration($config['http_client'], $container, $loader); } - if ($this->mailerConfigEnabled = $this->isConfigEnabled($container, $config['mailer'])) { + if ($this->readConfigEnabled('mailer', $container, $config['mailer'])) { $this->registerMailerConfiguration($config['mailer'], $container, $loader); + + if (!class_exists(MailerTestCommand::class)) { + $container->removeDefinition('console.command.mailer_test'); + } } - $propertyInfoEnabled = $this->isConfigEnabled($container, $config['property_info']); + $propertyInfoEnabled = $this->readConfigEnabled('property_info', $container, $config['property_info']); $this->registerHttpCacheConfiguration($config['http_cache'], $container, $config['http_method_override']); $this->registerEsiConfiguration($config['esi'], $container, $loader); $this->registerSsiConfiguration($config['ssi'], $container, $loader); @@ -400,7 +407,7 @@ public function load(array $configs, ContainerBuilder $container) $container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']); - if ($this->serializerConfigEnabled = $this->isConfigEnabled($container, $config['serializer'])) { + if ($this->readConfigEnabled('serializer', $container, $config['serializer'])) { if (!class_exists(\Symfony\Component\Serializer\Serializer::class)) { throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".'); } @@ -412,15 +419,15 @@ public function load(array $configs, ContainerBuilder $container) $this->registerPropertyInfoConfiguration($container, $loader); } - if (self::$lockConfigEnabled = $this->isConfigEnabled($container, $config['lock'])) { + if ($this->readConfigEnabled('lock', $container, $config['lock'])) { $this->registerLockConfiguration($config['lock'], $container, $loader); } - if ($this->isConfigEnabled($container, $config['semaphore'])) { + if ($this->readConfigEnabled('semaphore', $container, $config['semaphore'])) { $this->registerSemaphoreConfiguration($config['semaphore'], $container, $loader); } - if ($this->isConfigEnabled($container, $config['rate_limiter'])) { + if ($this->readConfigEnabled('rate_limiter', $container, $config['rate_limiter'])) { if (!interface_exists(LimiterInterface::class)) { throw new LogicException('Rate limiter support cannot be enabled as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".'); } @@ -428,7 +435,7 @@ public function load(array $configs, ContainerBuilder $container) $this->registerRateLimiterConfiguration($config['rate_limiter'], $container, $loader); } - if ($this->isConfigEnabled($container, $config['web_link'])) { + if ($this->readConfigEnabled('web_link', $container, $config['web_link'])) { if (!class_exists(HttpHeaderSerializer::class)) { throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed. Try running "composer require symfony/weblink".'); } @@ -436,7 +443,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('web_link.php'); } - if ($this->isConfigEnabled($container, $config['uid'])) { + if ($this->readConfigEnabled('uid', $container, $config['uid'])) { if (!class_exists(UuidFactory::class)) { throw new LogicException('Uid support cannot be enabled as the Uid component is not installed. Try running "composer require symfony/uid".'); } @@ -449,12 +456,11 @@ public function load(array $configs, ContainerBuilder $container) // register cache before session so both can share the connection services $this->registerCacheConfiguration($config['cache'], $container); - if ($this->isConfigEnabled($container, $config['session'])) { + if ($this->readConfigEnabled('session', $container, $config['session'])) { if (!\extension_loaded('session')) { throw new LogicException('Session support cannot be enabled as the session extension is not installed. See https://php.net/session.installation for instructions.'); } - $this->sessionConfigEnabled = true; $this->registerSessionConfiguration($config['session'], $container, $loader); if (!empty($config['test'])) { // test listener will replace the existing session listener @@ -467,28 +473,27 @@ public function load(array $configs, ContainerBuilder $container) // csrf depends on session being registered if (null === $config['csrf_protection']['enabled']) { - $config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle']); + $this->writeConfigEnabled('csrf_protection', $this->readConfigEnabled('session', $container, $config['session']) && !class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle']), $config['csrf_protection']); } $this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader); // form depends on csrf being registered - if ($this->isConfigEnabled($container, $config['form'])) { + if ($this->readConfigEnabled('form', $container, $config['form'])) { if (!class_exists(Form::class)) { throw new LogicException('Form support cannot be enabled as the Form component is not installed. Try running "composer require symfony/form".'); } - $this->formConfigEnabled = true; $this->registerFormConfiguration($config, $container, $loader); if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/form'])) { - $config['validation']['enabled'] = true; + $this->writeConfigEnabled('validation', true, $config['validation']); } else { $container->setParameter('validator.translation_domain', 'validators'); $container->removeDefinition('form.type_extension.form.validator'); $container->removeDefinition('form.type_guesser.validator'); } - if (!$this->isConfigEnabled($container, $config['html_sanitizer']) || !class_exists(TextTypeHtmlSanitizerExtension::class)) { + if (!$this->readConfigEnabled('html_sanitizer', $container, $config['html_sanitizer']) || !class_exists(TextTypeHtmlSanitizerExtension::class)) { $container->removeDefinition('form.type_extension.form.html_sanitizer'); } } else { @@ -499,10 +504,11 @@ public function load(array $configs, ContainerBuilder $container) $this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled); // messenger depends on validation being registered - if ($this->messengerConfigEnabled = $this->isConfigEnabled($container, $config['messenger'])) { + if ($this->readConfigEnabled('messenger', $container, $config['messenger'])) { $this->registerMessengerConfiguration($config['messenger'], $container, $loader, $config['validation']); } else { $container->removeDefinition('console.command.messenger_consume_messages'); + $container->removeDefinition('console.command.messenger_stats'); $container->removeDefinition('console.command.messenger_debug'); $container->removeDefinition('console.command.messenger_stop_workers'); $container->removeDefinition('console.command.messenger_setup_transports'); @@ -533,14 +539,14 @@ public function load(array $configs, ContainerBuilder $container) } // notifier depends on messenger, mailer being registered - if ($this->notifierConfigEnabled = $this->isConfigEnabled($container, $config['notifier'])) { + if ($this->readConfigEnabled('notifier', $container, $config['notifier'])) { $this->registerNotifierConfiguration($config['notifier'], $container, $loader); } // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier, serializer being registered $this->registerProfilerConfiguration($config['profiler'], $container, $loader); - if ($this->isConfigEnabled($container, $config['html_sanitizer'])) { + if ($this->readConfigEnabled('html_sanitizer', $container, $config['html_sanitizer'])) { if (!class_exists(HtmlSanitizerConfig::class)) { throw new LogicException('HtmlSanitizer support cannot be enabled as the HtmlSanitizer component is not installed. Try running "composer require symfony/html-sanitizer".'); } @@ -578,6 +584,8 @@ public function load(array $configs, ContainerBuilder $container) ->addTag('container.service_subscriber'); $container->registerForAutoconfiguration(ArgumentValueResolverInterface::class) ->addTag('controller.argument_value_resolver'); + $container->registerForAutoconfiguration(ValueResolverInterface::class) + ->addTag('controller.argument_value_resolver'); $container->registerForAutoconfiguration(AbstractController::class) ->addTag('controller.service_arguments'); $container->registerForAutoconfiguration(DataCollectorInterface::class) @@ -653,29 +661,18 @@ public function load(array $configs, ContainerBuilder $container) $definition->addTag('controller.service_arguments'); }); - if (class_exists(SerializedMessageStamp::class)) { - // symfony/messenger >= 6.1 - $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute, \ReflectionClass|\ReflectionMethod $reflector): void { - $tagAttributes = get_object_vars($attribute); - $tagAttributes['from_transport'] = $tagAttributes['fromTransport']; - unset($tagAttributes['fromTransport']); - if ($reflector instanceof \ReflectionMethod) { - if (isset($tagAttributes['method'])) { - throw new LogicException(sprintf('AsMessageHandler attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name)); - } - $tagAttributes['method'] = $reflector->getName(); + $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute, \ReflectionClass|\ReflectionMethod $reflector): void { + $tagAttributes = get_object_vars($attribute); + $tagAttributes['from_transport'] = $tagAttributes['fromTransport']; + unset($tagAttributes['fromTransport']); + if ($reflector instanceof \ReflectionMethod) { + if (isset($tagAttributes['method'])) { + throw new LogicException(sprintf('AsMessageHandler attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name)); } - $definition->addTag('messenger.message_handler', $tagAttributes); - }); - } else { - // symfony/messenger < 6.1 - $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute): void { - $tagAttributes = get_object_vars($attribute); - $tagAttributes['from_transport'] = $tagAttributes['fromTransport']; - unset($tagAttributes['fromTransport']); - $definition->addTag('messenger.message_handler', $tagAttributes); - }); - } + $tagAttributes['method'] = $reflector->getName(); + } + $definition->addTag('messenger.message_handler', $tagAttributes); + }); if (!$container->getParameter('kernel.debug')) { // remove tagged iterator argument for resource checkers @@ -701,9 +698,6 @@ public function load(array $configs, ContainerBuilder $container) ]); } - /** - * {@inheritdoc} - */ public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface { return new Configuration($container->getParameter('kernel.debug')); @@ -719,10 +713,10 @@ private function registerFormConfiguration(array $config, ContainerBuilder $cont $loader->load('form.php'); if (null === $config['form']['csrf_protection']['enabled']) { - $config['form']['csrf_protection']['enabled'] = $config['csrf_protection']['enabled']; + $this->writeConfigEnabled('form.csrf_protection', $config['csrf_protection']['enabled'], $config['form']['csrf_protection']); } - if ($this->isConfigEnabled($container, $config['form']['csrf_protection'])) { + if ($this->readConfigEnabled('form.csrf_protection', $container, $config['form']['csrf_protection'])) { if (!$container->hasDefinition('security.csrf.token_generator')) { throw new \LogicException('To use form CSRF protection, "framework.csrf_protection" must be enabled.'); } @@ -768,7 +762,7 @@ private function registerHttpCacheConfiguration(array $config, ContainerBuilder private function registerEsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('esi', $container, $config)) { $container->removeDefinition('fragment.renderer.esi'); return; @@ -779,7 +773,7 @@ private function registerEsiConfiguration(array $config, ContainerBuilder $conta private function registerSsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('ssi', $container, $config)) { $container->removeDefinition('fragment.renderer.ssi'); return; @@ -790,7 +784,7 @@ private function registerSsiConfiguration(array $config, ContainerBuilder $conta private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('fragments', $container, $config)) { $container->removeDefinition('fragment.renderer.hinclude'); return; @@ -804,7 +798,7 @@ private function registerFragmentsConfiguration(array $config, ContainerBuilder private function registerProfilerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('profiler', $container, $config)) { // this is needed for the WebProfiler to work even if the profiler is disabled $container->setParameter('data_collector.templates', []); @@ -815,37 +809,37 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $ $loader->load('collectors.php'); $loader->load('cache_debug.php'); - if ($this->formConfigEnabled) { + if ($this->isInitializedConfigEnabled('form')) { $loader->load('form_debug.php'); } - if ($this->validatorConfigEnabled) { + if ($this->isInitializedConfigEnabled('validation')) { $loader->load('validator_debug.php'); } - if ($this->translationConfigEnabled) { + if ($this->isInitializedConfigEnabled('translator')) { $loader->load('translation_debug.php'); $container->getDefinition('translator.data_collector')->setDecoratedService('translator'); } - if ($this->messengerConfigEnabled) { + if ($this->isInitializedConfigEnabled('messenger')) { $loader->load('messenger_debug.php'); } - if ($this->mailerConfigEnabled) { + if ($this->isInitializedConfigEnabled('mailer')) { $loader->load('mailer_debug.php'); } - if ($this->httpClientConfigEnabled) { + if ($this->isInitializedConfigEnabled('http_client')) { $loader->load('http_client_debug.php'); } - if ($this->notifierConfigEnabled) { + if ($this->isInitializedConfigEnabled('notifier')) { $loader->load('notifier_debug.php'); } - if ($this->serializerConfigEnabled && $config['collect_serializer_data']) { + if ($this->isInitializedConfigEnabled('serializer') && $config['collect_serializer_data']) { $loader->load('serializer_debug.php'); } @@ -882,9 +876,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $loader->load('workflow.php'); - $registryDefinition = $container->getDefinition('workflow.registry'); - - $workflows = []; + $registryDefinition = $container->getDefinition('.workflow.registry'); foreach ($config['workflows'] as $name => $workflow) { $type = $workflow['type']; @@ -973,8 +965,6 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $definitionDefinition->addArgument($initialMarking); $definitionDefinition->addArgument(new Reference(sprintf('%s.metadata_store', $workflowId))); - $workflows[$workflowId] = $definitionDefinition; - // Create MarkingStore if (isset($workflow['marking_store']['type'])) { $markingStoreDefinition = new ChildDefinition('workflow.marking_store.method'); @@ -993,6 +983,13 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $workflowDefinition->replaceArgument(3, $name); $workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']); + $workflowDefinition->addTag('workflow', ['name' => $name]); + if ('workflow' === $type) { + $workflowDefinition->addTag('workflow.workflow', ['name' => $name]); + } elseif ('state_machine' === $type) { + $workflowDefinition->addTag('workflow.state_machine', ['name' => $name]); + } + // Store to container $container->setDefinition($workflowId, $workflowDefinition); $container->setDefinition(sprintf('%s.definition', $workflowId), $definitionDefinition); @@ -1039,7 +1036,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } - if (!class_exists(Security::class)) { + if (!class_exists(AuthenticationEvents::class)) { throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security-core".'); } @@ -1062,9 +1059,6 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $container->setParameter('workflow.has_guard_listeners', true); } } - - $commandDumpDefinition = $container->getDefinition('console.command.workflow_dump'); - $commandDumpDefinition->setArgument(0, $workflows); } private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) @@ -1110,7 +1104,7 @@ private function registerDebugConfiguration(array $config, ContainerBuilder $con private function registerRouterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $enabledLocales = []) { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('router', $container, $config)) { $container->removeDefinition('console.command.router_debug'); $container->removeDefinition('console.command.router_match'); $container->removeDefinition('messenger.middleware.router_context'); @@ -1137,6 +1131,7 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co } $container->setParameter('router.resource', $config['resource']); + $container->setParameter('router.cache_dir', $config['cache_dir']); $router = $container->findDefinition('router.default'); $argument = $router->getArgument(2); $argument['strict_requirements'] = $config['strict_requirements']; @@ -1153,29 +1148,9 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co ->replaceArgument(0, $config['default_uri']); } - $container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class) - ->setPublic(false) - ->addTag('routing.loader', ['priority' => -10]) - ->setArguments([ - new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), - '%kernel.environment%', - ]); - - $container->register('routing.loader.annotation.directory', AnnotationDirectoryLoader::class) - ->setPublic(false) - ->addTag('routing.loader', ['priority' => -10]) - ->setArguments([ - new Reference('file_locator'), - new Reference('routing.loader.annotation'), - ]); - - $container->register('routing.loader.annotation.file', AnnotationFileLoader::class) - ->setPublic(false) - ->addTag('routing.loader', ['priority' => -10]) - ->setArguments([ - new Reference('file_locator'), - new Reference('routing.loader.annotation'), - ]); + if (!class_exists(Psr4DirectoryLoader::class)) { + $container->removeDefinition('routing.loader.psr4'); + } } private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) @@ -1316,7 +1291,7 @@ private function createVersion(ContainerBuilder $container, ?string $version, ?s private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales) { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('translator', $container, $config)) { $container->removeDefinition('console.command.translation_debug'); $container->removeDefinition('console.command.translation_extract'); $container->removeDefinition('console.command.translation_pull'); @@ -1331,6 +1306,14 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $container->removeDefinition('translation.locale_switcher'); } + if (ContainerBuilder::willBeAvailable('nikic/php-parser', Parser::class, ['symfony/translation']) + && ContainerBuilder::willBeAvailable('symfony/translation', PhpAstExtractor::class, ['symfony/framework-bundle']) + ) { + $container->removeDefinition('translation.extractor.php'); + } else { + $container->removeDefinition('translation.extractor.php_ast'); + } + $loader->load('translation_providers.php'); // Use the "real" translator instead of the identity default @@ -1504,7 +1487,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder private function registerValidationConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $propertyInfoEnabled) { - if (!$this->validatorConfigEnabled = $this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('validation', $container, $config)) { $container->removeDefinition('console.command.validator_debug'); return; @@ -1540,7 +1523,7 @@ private function registerValidationConfiguration(array $config, ContainerBuilder if (\array_key_exists('enable_annotations', $config) && $config['enable_annotations']) { $validatorBuilder->addMethodCall('enableAnnotationMapping', [true]); - if ($this->annotationsConfigEnabled) { + if ($this->isInitializedConfigEnabled('annotations')) { $validatorBuilder->addMethodCall('setDoctrineAnnotationReader', [new Reference('annotation_reader')]); } } @@ -1569,6 +1552,10 @@ private function registerValidationConfiguration(array $config, ContainerBuilder if (!class_exists(ExpressionLanguage::class)) { $container->removeDefinition('validator.expression_language'); } + + if (!class_exists(WhenValidator::class)) { + $container->removeDefinition('validator.when'); + } } private function registerValidatorMapping(ContainerBuilder $container, array $config, array &$files) @@ -1635,7 +1622,7 @@ private function registerMappingFilesFromConfig(ContainerBuilder $container, arr private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader) { - if (!$this->annotationsConfigEnabled) { + if (!$this->isInitializedConfigEnabled('annotations')) { return; } @@ -1690,7 +1677,7 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!$this->propertyAccessConfigEnabled = $this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('property_access', $container, $config)) { return; } @@ -1716,7 +1703,7 @@ private function registerPropertyAccessConfiguration(array $config, ContainerBui private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('secrets', $container, $config)) { $container->removeDefinition('console.command.secrets_set'); $container->removeDefinition('console.command.secrets_list'); $container->removeDefinition('console.command.secrets_remove'); @@ -1738,7 +1725,7 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c } if ($config['decryption_env_var']) { - if (!preg_match('/^(?:[-.\w]*+:)*+\w++$/', $config['decryption_env_var'])) { + if (!preg_match('/^(?:[-.\w\\\\]*+:)*+\w++$/', $config['decryption_env_var'])) { throw new InvalidArgumentException(sprintf('Invalid value "%s" set as "decryption_env_var": only "word" characters are allowed.', $config['decryption_env_var'])); } @@ -1756,7 +1743,7 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!$this->isConfigEnabled($container, $config)) { + if (!$this->readConfigEnabled('csrf_protection', $container, $config)) { return; } @@ -1764,7 +1751,7 @@ private function registerSecurityCsrfConfiguration(array $config, ContainerBuild throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require symfony/security-csrf".'); } - if (!$this->sessionConfigEnabled) { + if (!$this->isInitializedConfigEnabled('session')) { throw new \LogicException('CSRF protection needs sessions to be enabled.'); } @@ -1785,7 +1772,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $chainLoader = $container->getDefinition('serializer.mapping.chain_loader'); - if (!$this->propertyAccessConfigEnabled) { + if (!$this->isInitializedConfigEnabled('property_access')) { $container->removeAlias('serializer.property_accessor'); $container->removeDefinition('serializer.normalizer.object'); } @@ -1794,7 +1781,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->removeDefinition('serializer.encoder.yaml'); } - if (!class_exists(UnwrappingDenormalizer::class) || !$this->propertyAccessConfigEnabled) { + if (!class_exists(UnwrappingDenormalizer::class) || !$this->isInitializedConfigEnabled('property_access')) { $container->removeDefinition('serializer.denormalizer.unwrapping'); } @@ -1982,6 +1969,10 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder throw new LogicException('Messenger support cannot be enabled as the Messenger component is not installed. Try running "composer require symfony/messenger".'); } + if (!class_exists(StatsCommand::class)) { + $container->removeDefinition('console.command.messenger_stats'); + } + $loader->load('messenger.php'); if (!interface_exists(DenormalizerInterface::class)) { @@ -2023,12 +2014,9 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder foreach ($config['buses'] as $busId => $bus) { $middleware = $bus['middleware']; - if ($bus['default_middleware']) { - if ('allow_no_handlers' === $bus['default_middleware']) { - $defaultMiddleware['after'][1]['arguments'] = [true]; - } else { - unset($defaultMiddleware['after'][1]['arguments']); - } + if ($bus['default_middleware']['enabled']) { + $defaultMiddleware['after'][0]['arguments'] = [$bus['default_middleware']['allow_no_senders']]; + $defaultMiddleware['after'][1]['arguments'] = [$bus['default_middleware']['allow_no_handlers']]; // argument to add_bus_name_stamp_middleware $defaultMiddleware['before'][0]['arguments'] = [$busId]; @@ -2093,6 +2081,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $senderAliases = []; $transportRetryReferences = []; + $transportRateLimiterReferences = []; foreach ($config['transports'] as $name => $transport) { $serializerId = $transport['serializer'] ?? 'messenger.default_serializer'; $transportDefinition = (new Definition(TransportInterface::class)) @@ -2121,6 +2110,14 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $transportRetryReferences[$name] = new Reference($retryServiceId); } + + if ($transport['rate_limiter']) { + if (!interface_exists(LimiterInterface::class)) { + throw new LogicException('Rate limiter cannot be used within Messenger as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".'); + } + + $transportRateLimiterReferences[$name] = new Reference('limiter.'.$transport['rate_limiter']); + } } $senderReferences = []; @@ -2175,6 +2172,13 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $container->getDefinition('messenger.retry_strategy_locator') ->replaceArgument(0, $transportRetryReferences); + if (!$transportRateLimiterReferences) { + $container->removeDefinition('messenger.rate_limiter_locator'); + } else { + $container->getDefinition('messenger.rate_limiter_locator') + ->replaceArgument(0, $transportRateLimiterReferences); + } + if (\count($failureTransports) > 0) { $container->getDefinition('console.command.messenger_failed_messages_retry') ->replaceArgument(0, $config['failure_transport']); @@ -2315,7 +2319,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con } } - private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $profilerConfig) + private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { $loader->load('http_client.php'); @@ -2333,11 +2337,11 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $container->removeDefinition(HttpClient::class); } - if ($this->isConfigEnabled($container, $retryOptions)) { + if ($this->readConfigEnabled('http_client.retry_failed', $container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, 'http_client', $container); } - $httpClientId = ($retryOptions['enabled'] ?? false) ? 'http_client.retryable.inner' : ($this->isConfigEnabled($container, $profilerConfig) ? '.debug.http_client.inner' : 'http_client'); + $httpClientId = ($retryOptions['enabled'] ?? false) ? 'http_client.retryable.inner' : ($this->isInitializedConfigEnabled('profiler') ? '.debug.http_client.inner' : 'http_client'); foreach ($config['scoped_clients'] as $name => $scopeConfig) { if ('http_client' === $name) { throw new InvalidArgumentException(sprintf('Invalid scope name: "%s" is reserved.', $name)); @@ -2364,7 +2368,7 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder ; } - if ($this->isConfigEnabled($container, $retryOptions)) { + if ($this->readConfigEnabled('http_client.scoped_clients.'.$name.'retry_failed', $container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, $name, $container); } @@ -2446,6 +2450,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co $classToServices = [ GmailTransportFactory::class => 'mailer.transport_factory.gmail', + InfobipTransportFactory::class => 'mailer.transport_factory.infobip', MailgunTransportFactory::class => 'mailer.transport_factory.mailgun', MailjetTransportFactory::class => 'mailer.transport_factory.mailjet', MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', @@ -2482,6 +2487,10 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co } else { $container->removeDefinition('mailer.message_listener'); } + + if (!class_exists(MessengerTransportListener::class)) { + $container->removeDefinition('mailer.messenger_transport_listener'); + } } private function registerNotifierConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) @@ -2506,14 +2515,14 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ $container->removeAlias(TexterInterface::class); } - if ($this->mailerConfigEnabled) { + if ($this->isInitializedConfigEnabled('mailer')) { $sender = $container->getDefinition('mailer.envelope_listener')->getArgument(0); $container->getDefinition('notifier.channel.email')->setArgument(2, $sender); } else { $container->removeDefinition('notifier.channel.email'); } - if ($this->messengerConfigEnabled) { + if ($this->isInitializedConfigEnabled('messenger')) { if ($config['notification_on_failed_messages']) { $container->getDefinition('notifier.failed_message_listener')->addTag('kernel.event_subscriber'); } @@ -2538,7 +2547,9 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ $classToServices = [ AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms', AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', + ChatworkTransportFactory::class => 'notifier.transport_factory.chatwork', ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', + ContactEveryoneTransportFactory::class => 'notifier.transport_factory.contact-everyone', DiscordTransportFactory::class => 'notifier.transport_factory.discord', EngagespotTransportFactory::class => 'notifier.transport_factory.engagespot', EsendexTransportFactory::class => 'notifier.transport_factory.esendex', @@ -2576,6 +2587,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi', SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras', SmscTransportFactory::class => 'notifier.transport_factory.smsc', + SmsFactorTransportFactory::class => 'notifier.transport_factory.sms-factor', SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit', TelegramTransportFactory::class => 'notifier.transport_factory.telegram', TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', @@ -2583,6 +2595,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ TwilioTransportFactory::class => 'notifier.transport_factory.twilio', VonageTransportFactory::class => 'notifier.transport_factory.vonage', YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', + ZendeskTransportFactory::class => 'notifier.transport_factory.zendesk', ZulipTransportFactory::class => 'notifier.transport_factory.zulip', ]; @@ -2630,34 +2643,68 @@ private function registerRateLimiterConfiguration(array $config, ContainerBuilde $loader->load('rate_limiter.php'); foreach ($config['limiters'] as $name => $limiterConfig) { - self::registerRateLimiter($container, $name, $limiterConfig); + // default configuration (when used by other DI extensions) + $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; + + $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')); + + if (null !== $limiterConfig['lock_factory']) { + if (!interface_exists(LockInterface::class)) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name)); + } + + if (!$this->isInitializedConfigEnabled('lock')) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be configured.', $name)); + } + + $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory'])); + } + unset($limiterConfig['lock_factory']); + + if (null === $storageId = $limiterConfig['storage_service'] ?? null) { + $container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool'])); + } + + $limiter->replaceArgument(1, new Reference($storageId)); + unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']); + + $limiterConfig['id'] = $name; + $limiter->replaceArgument(0, $limiterConfig); + + $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter'); } } + /** + * @deprecated since Symfony 6.2 + */ public static function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig) { + trigger_deprecation('symfony/framework-bundle', '6.2', 'The "%s()" method is deprecated.', __METHOD__); + // default configuration (when used by other DI extensions) $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')); if (null !== $limiterConfig['lock_factory']) { - if (!self::$lockConfigEnabled) { - throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed and configured.', $name)); + if (!interface_exists(LockInterface::class)) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name)); + } + if (!$container->hasDefinition('lock.factory.abstract')) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be configured.', $name)); } $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory'])); } unset($limiterConfig['lock_factory']); - $storageId = $limiterConfig['storage_service'] ?? null; - if (null === $storageId) { + if (null === $storageId = $limiterConfig['storage_service'] ?? null) { $container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool'])); } $limiter->replaceArgument(1, new Reference($storageId)); - unset($limiterConfig['storage_service']); - unset($limiterConfig['cache_pool']); + unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']); $limiterConfig['id'] = $name; $limiter->replaceArgument(0, $limiterConfig); @@ -2775,22 +2822,20 @@ private function resolveTrustedHeaders(array $headers): int $trustedHeaders = 0; foreach ($headers as $h) { - switch ($h) { - case 'forwarded': $trustedHeaders |= Request::HEADER_FORWARDED; break; - case 'x-forwarded-for': $trustedHeaders |= Request::HEADER_X_FORWARDED_FOR; break; - case 'x-forwarded-host': $trustedHeaders |= Request::HEADER_X_FORWARDED_HOST; break; - case 'x-forwarded-proto': $trustedHeaders |= Request::HEADER_X_FORWARDED_PROTO; break; - case 'x-forwarded-port': $trustedHeaders |= Request::HEADER_X_FORWARDED_PORT; break; - case 'x-forwarded-prefix': $trustedHeaders |= Request::HEADER_X_FORWARDED_PREFIX; break; - } + $trustedHeaders |= match ($h) { + 'forwarded' => Request::HEADER_FORWARDED, + 'x-forwarded-for' => Request::HEADER_X_FORWARDED_FOR, + 'x-forwarded-host' => Request::HEADER_X_FORWARDED_HOST, + 'x-forwarded-proto' => Request::HEADER_X_FORWARDED_PROTO, + 'x-forwarded-port' => Request::HEADER_X_FORWARDED_PORT, + 'x-forwarded-prefix' => Request::HEADER_X_FORWARDED_PREFIX, + default => 0, + }; } return $trustedHeaders; } - /** - * {@inheritdoc} - */ public function getXsdValidationBasePath(): string|false { return \dirname(__DIR__).'/Resources/config/schema'; @@ -2800,4 +2845,33 @@ public function getNamespace(): string { return 'http://symfony.com/schema/dic/symfony'; } + + protected function isConfigEnabled(ContainerBuilder $container, array $config): bool + { + throw new \LogicException('To prevent using outdated configuration, you must use the "readConfigEnabled" method instead.'); + } + + private function isInitializedConfigEnabled(string $path): bool + { + if (isset($this->configsEnabled[$path])) { + return $this->configsEnabled[$path]; + } + + throw new LogicException(sprintf('Can not read config enabled at "%s" because it has not been initialized.', $path)); + } + + private function readConfigEnabled(string $path, ContainerBuilder $container, array $config): bool + { + return $this->configsEnabled[$path] ??= parent::isConfigEnabled($container, $config); + } + + private function writeConfigEnabled(string $path, bool $value, array &$config): void + { + if (isset($this->configsEnabled[$path])) { + throw new LogicException('Can not change config enabled because it has already been read.'); + } + + $this->configsEnabled[$path] = $value; + $config['enabled'] = $value; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php b/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php index 102892b2f426c..f312a7afe4946 100644 --- a/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php +++ b/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php @@ -60,9 +60,6 @@ public function __construct(KernelInterface $kernel, string|StoreInterface $cach parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge($this->options, $this->getOptions())); } - /** - * {@inheritdoc} - */ protected function forward(Request $request, bool $catch = false, Response $entry = null): Response { $this->getKernel()->boot(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php index dbf77b3bd94bd..fa04ff332433d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php @@ -104,9 +104,6 @@ private function getBundlesPath(): string return $this->getConfigDir().'/bundles.php'; } - /** - * {@inheritdoc} - */ public function getCacheDir(): string { if (isset($_SERVER['APP_CACHE_DIR'])) { @@ -116,17 +113,11 @@ public function getCacheDir(): string return parent::getCacheDir(); } - /** - * {@inheritdoc} - */ public function getLogDir(): string { return $_SERVER['APP_LOG_DIR'] ?? parent::getLogDir(); } - /** - * {@inheritdoc} - */ public function registerBundles(): iterable { $contents = require $this->getBundlesPath(); @@ -137,9 +128,6 @@ public function registerBundles(): iterable } } - /** - * {@inheritdoc} - */ public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(function (ContainerBuilder $container) use ($loader) { diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index 4fb751d4bfb32..b7e8e27eacbb5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -34,9 +34,6 @@ class KernelBrowser extends HttpKernelBrowser private bool $profiler = false; private bool $reboot = true; - /** - * {@inheritdoc} - */ public function __construct(KernelInterface $kernel, array $server = [], History $history = null, CookieJar $cookieJar = null) { parent::__construct($kernel, $server, $history, $cookieJar); @@ -147,8 +144,6 @@ public function loginUser(object $user, string $firewallContext = 'main'): stati } /** - * {@inheritdoc} - * * @param Request $request */ protected function doRequest(object $request): Response @@ -173,8 +168,6 @@ protected function doRequest(object $request): Response } /** - * {@inheritdoc} - * * @param Request $request */ protected function doRequestInProcess(object $request): Response diff --git a/src/Symfony/Bundle/FrameworkBundle/README.md b/src/Symfony/Bundle/FrameworkBundle/README.md index db59697577dde..76c7700fa03af 100644 --- a/src/Symfony/Bundle/FrameworkBundle/README.md +++ b/src/Symfony/Bundle/FrameworkBundle/README.md @@ -4,19 +4,6 @@ FrameworkBundle FrameworkBundle provides a tight integration between Symfony components and the Symfony full-stack framework. -Sponsor -------- - -The FrameworkBundle for Symfony 6.1 is [backed][1] by [alximy][2]. - -A team of passionate humans from very different backgrounds, sharing their love of -PHP, Symfony and its ecosystem. Their CTO, Expert developers, tech leads, can help -you learn or develop the tools you need, and perform audits or tailored workshops. -They value contributing to the Open Source community and are willing to mentor new -contributors in their team or yours. - -Help Symfony by [sponsoring][3] its development! - Resources --------- @@ -24,7 +11,3 @@ Resources * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) - -[1]: https://symfony.com/backers -[2]: https://alximy.io/ -[3]: https://symfony.com/sponsor diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php index c8ff77f1e795d..d64cd058e61f8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php @@ -47,6 +47,7 @@ use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand; use Symfony\Component\Messenger\Command\FailedMessagesShowCommand; use Symfony\Component\Messenger\Command\SetupTransportsCommand; +use Symfony\Component\Messenger\Command\StatsCommand; use Symfony\Component\Messenger\Command\StopWorkersCommand; use Symfony\Component\Translation\Command\TranslationPullCommand; use Symfony\Component\Translation\Command\TranslationPushCommand; @@ -159,6 +160,7 @@ [], // Receiver names service('messenger.listener.reset_services')->nullOnInvalid(), [], // Bus names + service('messenger.rate_limiter_locator')->nullOnInvalid(), ]) ->tag('console.command') ->tag('monolog.logger', ['channel' => 'messenger']) @@ -189,6 +191,7 @@ service('messenger.routable_message_bus'), service('event_dispatcher'), service('logger'), + service('messenger.transport.native_php_serializer')->nullOnInvalid(), ]) ->tag('console.command') @@ -196,6 +199,7 @@ ->args([ abstract_arg('Default failure receiver name'), abstract_arg('Receivers'), + service('messenger.transport.native_php_serializer')->nullOnInvalid(), ]) ->tag('console.command') @@ -203,6 +207,14 @@ ->args([ abstract_arg('Default failure receiver name'), abstract_arg('Receivers'), + service('messenger.transport.native_php_serializer')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.messenger_stats', StatsCommand::class) + ->args([ + service('messenger.receiver_locator'), + abstract_arg('Receivers names'), ]) ->tag('console.command') @@ -274,6 +286,9 @@ ->tag('console.command', ['command' => 'translation:push']) ->set('console.command.workflow_dump', WorkflowDumpCommand::class) + ->args([ + tagged_locator('workflow', 'name'), + ]) ->tag('console.command') ->set('console.command.xliff_lint', XliffLintCommand::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.php index 51ad286273e06..9eb545ca268ea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.php @@ -11,9 +11,11 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Mailer\Command\MailerTestCommand; use Symfony\Component\Mailer\EventListener\EnvelopeListener; use Symfony\Component\Mailer\EventListener\MessageListener; use Symfony\Component\Mailer\EventListener\MessageLoggerListener; +use Symfony\Component\Mailer\EventListener\MessengerTransportListener; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mailer\Messenger\MessageHandler; @@ -72,5 +74,14 @@ ->set('mailer.message_logger_listener', MessageLoggerListener::class) ->tag('kernel.event_subscriber') ->tag('kernel.reset', ['method' => 'reset']) + + ->set('mailer.messenger_transport_listener', MessengerTransportListener::class) + ->tag('kernel.event_subscriber') + + ->set('console.command.mailer_test', MailerTestCommand::class) + ->args([ + service('mailer.transports'), + ]) + ->tag('console.command') ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php index 7bddfa7567cee..7e799bd1ee262 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php @@ -13,6 +13,7 @@ use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; +use Symfony\Component\Mailer\Bridge\Infobip\Transport\InfobipTransportFactory; use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; @@ -45,6 +46,10 @@ ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.infobip', InfobipTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + ->set('mailer.transport_factory.mailchimp', MandrillTransportFactory::class) ->parent('mailer.transport_factory.abstract') ->tag('mailer.transport_factory') @@ -74,8 +79,8 @@ ->tag('mailer.transport_factory') ->set('mailer.transport_factory.sendinblue', SendinblueTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') ->set('mailer.transport_factory.ohmysmtp', OhMySmtpTransportFactory::class) ->parent('mailer.transport_factory.abstract') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php index 813d503000de4..13e8dad627835 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php @@ -55,6 +55,7 @@ abstract_arg('senders service locator'), ]) ->set('messenger.middleware.send_message', SendMessageMiddleware::class) + ->abstract() ->args([ service('messenger.senders_locator'), service('event_dispatcher'), @@ -159,6 +160,11 @@ abstract_arg('max delay ms'), ]) + // rate limiter + ->set('messenger.rate_limiter_locator', ServiceLocator::class) + ->args([[]]) + ->tag('container.service_locator') + // worker event listener ->set('messenger.retry.send_failed_message_for_retry_listener', SendFailedMessageForRetryListener::class) ->args([ diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 819177c337787..237ae18a59eb7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -13,7 +13,9 @@ use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; +use Symfony\Component\Notifier\Bridge\Chatwork\ChatworkTransportFactory; use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; +use Symfony\Component\Notifier\Bridge\ContactEveryone\ContactEveryoneTransportFactory; use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; use Symfony\Component\Notifier\Bridge\Engagespot\EngagespotTransportFactory; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; @@ -51,6 +53,7 @@ use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; +use Symfony\Component\Notifier\Bridge\SmsFactor\SmsFactorTransportFactory; use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; @@ -58,6 +61,7 @@ use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; +use Symfony\Component\Notifier\Bridge\Zendesk\ZendeskTransportFactory; use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use Symfony\Component\Notifier\Transport\AbstractTransportFactory; use Symfony\Component\Notifier\Transport\NullTransportFactory; @@ -197,6 +201,10 @@ ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.contact-everyone', ContactEveryoneTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->set('notifier.transport_factory.amazon-sns', AmazonSnsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') @@ -219,6 +227,10 @@ ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.sms-factor', SmsFactorTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->set('notifier.transport_factory.message-bird', MessageBirdTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') @@ -266,5 +278,14 @@ ->set('notifier.transport_factory.engagespot', EngagespotTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.zendesk', ZendeskTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.chatwork', ChatworkTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php index 09e340ff8aedd..86a7cf874629c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php @@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer; use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; +use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader; use Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader; use Symfony\Bundle\FrameworkBundle\Routing\RedirectableCompiledUrlMatcher; use Symfony\Bundle\FrameworkBundle\Routing\Router; @@ -23,10 +24,13 @@ use Symfony\Component\Routing\Generator\CompiledUrlGenerator; use Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; +use Symfony\Component\Routing\Loader\AnnotationFileLoader; use Symfony\Component\Routing\Loader\ContainerLoader; use Symfony\Component\Routing\Loader\DirectoryLoader; use Symfony\Component\Routing\Loader\GlobFileLoader; use Symfony\Component\Routing\Loader\PhpFileLoader; +use Symfony\Component\Routing\Loader\Psr4DirectoryLoader; use Symfony\Component\Routing\Loader\XmlFileLoader; use Symfony\Component\Routing\Loader\YamlFileLoader; use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; @@ -88,6 +92,33 @@ ]) ->tag('routing.loader') + ->set('routing.loader.annotation', AnnotatedRouteControllerLoader::class) + ->args([ + service('annotation_reader')->nullOnInvalid(), + '%kernel.environment%', + ]) + ->tag('routing.loader', ['priority' => -10]) + + ->set('routing.loader.annotation.directory', AnnotationDirectoryLoader::class) + ->args([ + service('file_locator'), + service('routing.loader.annotation'), + ]) + ->tag('routing.loader', ['priority' => -10]) + + ->set('routing.loader.annotation.file', AnnotationFileLoader::class) + ->args([ + service('file_locator'), + service('routing.loader.annotation'), + ]) + ->tag('routing.loader', ['priority' => -10]) + + ->set('routing.loader.psr4', Psr4DirectoryLoader::class) + ->args([ + service('file_locator'), + ]) + ->tag('routing.loader', ['priority' => -10]) + ->set('routing.loader', DelegatingLoader::class) ->public() ->args([ @@ -101,7 +132,7 @@ service(ContainerInterface::class), param('router.resource'), [ - 'cache_dir' => param('kernel.cache_dir'), + 'cache_dir' => param('router.cache_dir'), 'debug' => param('kernel.debug'), 'generator_class' => CompiledUrlGenerator::class, 'generator_dumper_class' => CompiledUrlGeneratorDumper::class, diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index cd53d88db00ac..78207dd37a6ef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -426,7 +426,7 @@ - + @@ -436,6 +436,12 @@ + + + + + + @@ -554,6 +560,7 @@ + @@ -566,10 +573,11 @@ + - + @@ -723,6 +731,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php index 5ffc869ca7bd9..edf1987e32adc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php @@ -42,6 +42,7 @@ use Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; @@ -127,6 +128,7 @@ ->tag('serializer.normalizer', ['priority' => -1000]) ->alias(ObjectNormalizer::class, 'serializer.normalizer.object') + ->deprecate('symfony/serializer', '6.2', 'The "%alias_id%" service alias is deprecated, type-hint against "'.NormalizerInterface::class.'" or implement "'.NormalizerAwareInterface::class.'" instead.') ->set('serializer.normalizer.property', PropertyNormalizer::class) ->args([ @@ -138,6 +140,7 @@ ]) ->alias(PropertyNormalizer::class, 'serializer.normalizer.property') + ->deprecate('symfony/serializer', '6.2', 'The "%alias_id%" service alias is deprecated, type-hint against "'.NormalizerInterface::class.'" or implement "'.NormalizerAwareInterface::class.'" instead.') ->set('serializer.denormalizer.array', ArrayDenormalizer::class) ->tag('serializer.normalizer', ['priority' => -990]) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php index f662343e331b8..40b6a5ac6c445 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php @@ -11,8 +11,12 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; use Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer; use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; +use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\Clock\NativeClock; +use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; use Symfony\Component\Config\ResourceCheckerConfigCacheFactory; use Symfony\Component\Console\ConsoleEvents; @@ -26,7 +30,10 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface as EventDispatcherInterfaceComponentAlias; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Form\FormEvents; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpFoundation\UrlHelper; use Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; @@ -73,6 +80,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] ->tag('event_dispatcher.dispatcher', ['name' => 'event_dispatcher']) ->alias(EventDispatcherInterfaceComponentAlias::class, 'event_dispatcher') ->alias(EventDispatcherInterface::class, 'event_dispatcher') + ->alias(PsrEventDispatcherInterface::class, 'event_dispatcher') ->set('http_kernel', HttpKernel::class) ->public() @@ -81,6 +89,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] service('controller_resolver'), service('request_stack'), service('argument_resolver'), + false, ]) ->tag('container.hot_path') ->tag('container.preload', ['class' => HttpKernelRunner::class]) @@ -218,5 +227,14 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] ->set('config_builder.warmer', ConfigBuilderCacheWarmer::class) ->args([service(KernelInterface::class), service('logger')->nullOnInvalid()]) ->tag('kernel.cache_warmer') + + ->set('clock', NativeClock::class) + ->alias(ClockInterface::class, 'clock') + + // register as abstract and excluded, aka not-autowirable types + ->set(LoaderInterface::class)->abstract()->tag('container.excluded') + ->set(Request::class)->abstract()->tag('container.excluded') + ->set(Response::class)->abstract()->tag('container.excluded') + ->set(SessionInterface::class)->abstract()->tag('container.excluded') ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php index fadaff6d2b596..265fb6da336a0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php @@ -26,7 +26,11 @@ use Symfony\Component\Translation\Dumper\YamlFileDumper; use Symfony\Component\Translation\Extractor\ChainExtractor; use Symfony\Component\Translation\Extractor\ExtractorInterface; +use Symfony\Component\Translation\Extractor\PhpAstExtractor; use Symfony\Component\Translation\Extractor\PhpExtractor; +use Symfony\Component\Translation\Extractor\Visitor\ConstraintVisitor; +use Symfony\Component\Translation\Extractor\Visitor\TranslatableMessageVisitor; +use Symfony\Component\Translation\Extractor\Visitor\TransMethodVisitor; use Symfony\Component\Translation\Formatter\MessageFormatter; use Symfony\Component\Translation\Loader\CsvFileLoader; use Symfony\Component\Translation\Loader\IcuDatFileLoader; @@ -151,6 +155,19 @@ ->set('translation.extractor.php', PhpExtractor::class) ->tag('translation.extractor', ['alias' => 'php']) + ->set('translation.extractor.php_ast', PhpAstExtractor::class) + ->args([tagged_iterator('translation.extractor.visitor')]) + ->tag('translation.extractor', ['alias' => 'php']) + + ->set('translation.extractor.visitor.trans_method', TransMethodVisitor::class) + ->tag('translation.extractor.visitor') + + ->set('translation.extractor.visitor.translatable_message', TranslatableMessageVisitor::class) + ->tag('translation.extractor.visitor') + + ->set('translation.extractor.visitor.constraint', ConstraintVisitor::class) + ->tag('translation.extractor.visitor') + ->set('translation.reader', TranslationReader::class) ->alias(TranslationReaderInterface::class, 'translation.reader') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php index 3fd066ee37f1f..c397e73d42505 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php @@ -17,6 +17,7 @@ use Symfony\Component\Validator\Constraints\EmailValidator; use Symfony\Component\Validator\Constraints\ExpressionValidator; use Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator; +use Symfony\Component\Validator\Constraints\WhenValidator; use Symfony\Component\Validator\ContainerConstraintValidatorFactory; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\Validation; @@ -95,6 +96,12 @@ 'alias' => NotCompromisedPasswordValidator::class, ]) + ->set('validator.when', WhenValidator::class) + ->args([service('validator.expression_language')->nullOnInvalid()]) + ->tag('validator.constraint_validator', [ + 'alias' => WhenValidator::class, + ]) + ->set('validator.property_info_loader', PropertyInfoLoader::class) ->args([ service('property_info'), diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php index d28e88995f84c..82ceb2e077da4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php @@ -24,6 +24,7 @@ use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; use Symfony\Component\HttpKernel\Controller\ErrorController; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\EventListener\CacheAttributeListener; use Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener; use Symfony\Component\HttpKernel\EventListener\ErrorListener; use Symfony\Component\HttpKernel\EventListener\LocaleListener; @@ -117,5 +118,9 @@ ]) ->tag('kernel.event_subscriber') ->tag('monolog.logger', ['channel' => 'request']) + + ->set('controller.cache_attribute_listener', CacheAttributeListener::class) + ->tag('kernel.event_subscriber') + ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.php index b6c784bdbeaa9..4b8875da73424 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.php @@ -39,8 +39,11 @@ ->abstract() ->set('workflow.marking_store.method', MethodMarkingStore::class) ->abstract() - ->set('workflow.registry', Registry::class) - ->alias(Registry::class, 'workflow.registry') + ->set('.workflow.registry', Registry::class) + ->alias(Registry::class, '.workflow.registry') + ->deprecate('symfony/workflow', '6.2', 'The "%alias_id%" alias is deprecated since Symfony 6.2 and will be removed in Symfony 7.0. Inject the workflow directly.') + ->alias('workflow.registry', '.workflow.registry') + ->deprecate('symfony/workflow', '6.2', 'The "%alias_id%" service is deprecated since Symfony 6.2 and will be removed in Symfony 7.0. Inject the workflow directly.') ->set('workflow.security.expression_language', ExpressionLanguage::class) ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php index a0fefe7fd2fab..5e8d8f354dc96 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php @@ -40,9 +40,6 @@ public function __construct(LoaderResolverInterface $resolver, array $defaultOpt parent::__construct($resolver); } - /** - * {@inheritdoc} - */ public function load(mixed $resource, string $type = null): RouteCollection { if ($this->loading) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RedirectableCompiledUrlMatcher.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RedirectableCompiledUrlMatcher.php index dba9d6d9613d1..538427aae6cf2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RedirectableCompiledUrlMatcher.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RedirectableCompiledUrlMatcher.php @@ -21,9 +21,6 @@ */ class RedirectableCompiledUrlMatcher extends CompiledUrlMatcher implements RedirectableUrlMatcherInterface { - /** - * {@inheritdoc} - */ public function redirect(string $path, string $route, string $scheme = null): array { return [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index 8fda00a771fe0..ac5db387de808 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -59,9 +59,6 @@ public function __construct(ContainerInterface $container, mixed $resource, arra $this->defaultLocale = $defaultLocale; } - /** - * {@inheritdoc} - */ public function getRouteCollection(): RouteCollection { if (null === $this->collection) { @@ -84,8 +81,6 @@ public function getRouteCollection(): RouteCollection } /** - * {@inheritdoc} - * * @return string[] A list of classes to preload on PHP 7.4+ */ public function warmUp(string $cacheDir): array @@ -193,9 +188,6 @@ private function resolve(mixed $value): mixed return str_replace('%%', '%', $escapedValue); } - /** - * {@inheritdoc} - */ public static function getSubscribedServices(): array { return [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php index eeecbbb68b683..cfa4bab13c3a6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php +++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/AbstractVault.php @@ -13,8 +13,6 @@ /** * @author Nicolas Grekas - * - * @internal */ abstract class AbstractVault { diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/DotenvVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/DotenvVault.php index 4fa7c40bf8d72..db4891c34b337 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Secrets/DotenvVault.php +++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/DotenvVault.php @@ -13,8 +13,6 @@ /** * @author Nicolas Grekas - * - * @internal */ class DotenvVault extends AbstractVault { diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php index ff1cd7fb87908..e8ce7d8f95eec 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php +++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php @@ -18,8 +18,6 @@ * @author Tobias Schultze * @author Jérémy Derussé * @author Nicolas Grekas - * - * @internal */ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface { diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php index d189b88db5799..32bac9c42f02b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -26,6 +26,7 @@ abstract class KernelTestCase extends TestCase { use MailerAssertionsTrait; + use NotificationAssertionsTrait; protected static $class; diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php new file mode 100644 index 0000000000000..163c2a9719c55 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\Notifier\Event\MessageEvent; +use Symfony\Component\Notifier\Event\NotificationEvents; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Test\Constraint as NotifierConstraint; + +/* + * @author Smaïne Milianni + */ +trait NotificationAssertionsTrait +{ + public static function assertNotificationCount(int $count, string $transportName = null, string $message = ''): void + { + self::assertThat(self::getNotificationEvents(), new NotifierConstraint\NotificationCount($count, $transportName), $message); + } + + public static function assertQueuedNotificationCount(int $count, string $transportName = null, string $message = ''): void + { + self::assertThat(self::getMessageMailerEvents(), new NotifierConstraint\NotificationCount($count, $transportName, true), $message); + } + + public static function assertNotificationIsQueued(MessageEvent $event, string $message = ''): void + { + self::assertThat($event, new NotifierConstraint\NotificationIsQueued(), $message); + } + + public static function assertNotificationIsNotQueued(MessageEvent $event, string $message = ''): void + { + self::assertThat($event, new LogicalNot(new NotifierConstraint\NotificationIsQueued()), $message); + } + + public static function assertNotificationSubjectContains(MessageInterface $notification, string $text, string $message = ''): void + { + self::assertThat($notification, new NotifierConstraint\NotificationSubjectContains($text), $message); + } + + public static function assertNotificationSubjectNotContains(MessageInterface $notification, string $text, string $message = ''): void + { + self::assertThat($notification, new LogicalNot(new NotifierConstraint\NotificationSubjectContains($text)), $message); + } + + public static function assertNotificationTransportIsEqual(MessageInterface $notification, string $transportName, string $message = ''): void + { + self::assertThat($notification, new NotifierConstraint\NotificationTransportIsEqual($transportName), $message); + } + + public static function assertNotificationTransportIsNotEqual(MessageInterface $notification, string $transportName, string $message = ''): void + { + self::assertThat($notification, new LogicalNot(new NotifierConstraint\NotificationTransportIsEqual($transportName)), $message); + } + + /** + * @return MessageEvent[] + */ + public static function getNotifierEvents(string $transportName = null): array + { + return self::getNotificationEvents()->getEvents($transportName); + } + + public static function getNotifierEvent(int $index = 0, string $transportName = null): ?MessageEvent + { + return self::getNotifierEvents($transportName)[$index] ?? null; + } + + /** + * @return MessageInterface[] + */ + public static function getNotifierMessages(string $transportName = null): array + { + return self::getNotificationEvents()->getMessages($transportName); + } + + public static function getNotifierMessage(int $index = 0, string $transportName = null): ?MessageInterface + { + return self::getNotifierMessages($transportName)[$index] ?? null; + } + + public static function getNotificationEvents(): NotificationEvents + { + $container = static::getContainer(); + if ($container->has('notifier.logger_notification_listener')) { + return $container->get('notifier.logger_notification_listener')->getEvents(); + } + + static::fail('A client must have Notifier enabled to make notifications assertions. Did you forget to require symfony/notifier?'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php index 321ea373d85f1..883928087ea03 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php @@ -37,105 +37,66 @@ public function __construct(KernelInterface $kernel, string $privateServicesLoca $this->privateServicesLocatorId = $privateServicesLocatorId; } - /** - * {@inheritdoc} - */ public function compile() { $this->getPublicContainer()->compile(); } - /** - * {@inheritdoc} - */ public function isCompiled(): bool { return $this->getPublicContainer()->isCompiled(); } - /** - * {@inheritdoc} - */ public function getParameterBag(): ParameterBagInterface { return $this->getPublicContainer()->getParameterBag(); } - /** - * {@inheritdoc} - */ public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null { return $this->getPublicContainer()->getParameter($name); } - /** - * {@inheritdoc} - */ public function hasParameter(string $name): bool { return $this->getPublicContainer()->hasParameter($name); } - /** - * {@inheritdoc} - */ public function setParameter(string $name, mixed $value) { $this->getPublicContainer()->setParameter($name, $value); } - /** - * {@inheritdoc} - */ public function set(string $id, mixed $service) { $this->getPublicContainer()->set($id, $service); } - /** - * {@inheritdoc} - */ public function has(string $id): bool { return $this->getPublicContainer()->has($id) || $this->getPrivateContainer()->has($id); } - /** - * {@inheritdoc} - */ public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE): ?object { return $this->getPrivateContainer()->has($id) ? $this->getPrivateContainer()->get($id) : $this->getPublicContainer()->get($id, $invalidBehavior); } - /** - * {@inheritdoc} - */ public function initialized(string $id): bool { return $this->getPublicContainer()->initialized($id); } - /** - * {@inheritdoc} - */ public function reset() { // ignore the call } - /** - * {@inheritdoc} - */ public function getServiceIds(): array { return $this->getPublicContainer()->getServiceIds(); } - /** - * {@inheritdoc} - */ public function getRemovedIds(): array { return $this->getPublicContainer()->getRemovedIds(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php index 8a5a023e3b7ac..d3f8e2c9be5a4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php @@ -126,7 +126,7 @@ public function testCacheIsWarmedWithOldContainer() \Closure::bind(function (Container $class) { unset($class->loadedDynamicParameters['kernel.build_dir']); unset($class->parameters['kernel.build_dir']); - }, null, \get_class($container))($container); + }, null, $container::class)($container); $input = new ArrayInput(['cache:clear']); $application = new Application($kernel); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/WorkflowDumpCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/WorkflowDumpCommandTest.php index 13a63b40d97fa..cefc5dacd8e50 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/WorkflowDumpCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/WorkflowDumpCommandTest.php @@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\Command\WorkflowDumpCommand; use Symfony\Component\Console\Application; use Symfony\Component\Console\Tester\CommandCompletionTester; +use Symfony\Component\DependencyInjection\ServiceLocator; class WorkflowDumpCommandTest extends TestCase { @@ -24,7 +25,7 @@ class WorkflowDumpCommandTest extends TestCase public function testComplete(array $input, array $expectedSuggestions) { $application = new Application(); - $application->add(new WorkflowDumpCommand([])); + $application->add(new WorkflowDumpCommand(new ServiceLocator([]))); $tester = new CommandCompletionTester($application->find('workflow:dump')); $suggestions = $tester->complete($input, 2); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php index da6997df578c4..f5dd7703eacfb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php @@ -392,7 +392,55 @@ public function testRenderTwig() $this->assertEquals('bar', $controller->render('foo')->getContent()); } - public function testRenderFormNew() + public function testRenderViewWithForm() + { + $formView = new FormView(); + + $form = $this->getMockBuilder(FormInterface::class)->getMock(); + $form->expects($this->once())->method('createView')->willReturn($formView); + + $twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock(); + $twig->expects($this->once())->method('render')->with('foo', ['bar' => $formView])->willReturn('bar'); + + $container = new Container(); + $container->set('twig', $twig); + + $controller = $this->createController(); + $controller->setContainer($container); + + $content = $controller->renderView('foo', ['bar' => $form]); + + $this->assertSame('bar', $content); + } + + public function testRenderWithFormSubmittedAndInvalid() + { + $formView = new FormView(); + + $form = $this->getMockBuilder(FormInterface::class)->getMock(); + $form->expects($this->once())->method('createView')->willReturn($formView); + $form->expects($this->once())->method('isSubmitted')->willReturn(true); + $form->expects($this->once())->method('isValid')->willReturn(false); + + $twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock(); + $twig->expects($this->once())->method('render')->with('foo', ['bar' => $formView])->willReturn('bar'); + + $container = new Container(); + $container->set('twig', $twig); + + $controller = $this->createController(); + $controller->setContainer($container); + + $response = $controller->render('foo', ['bar' => $form]); + + $this->assertSame(422, $response->getStatusCode()); + $this->assertSame('bar', $response->getContent()); + } + + /** + * @group legacy + */ + public function testRenderForm() { $formView = new FormView(); @@ -414,6 +462,9 @@ public function testRenderFormNew() $this->assertSame('bar', $response->getContent()); } + /** + * @group legacy + */ public function testRenderFormSubmittedAndInvalid() { $formView = new FormView(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php index 5fb3e774a709d..6f5b870e3350e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php @@ -172,7 +172,7 @@ class ContainerAwareController implements ContainerAwareInterface { private $container; - public function setContainer(ContainerInterface $container = null) + public function setContainer(?ContainerInterface $container) { $this->container = $container; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php index 65f047426ae44..bb7d4f1f4ae4d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php @@ -105,12 +105,12 @@ public function testValidCollectorWithTemplateUsingAutoconfigure(TemplateAwareDa $profilerDefinition = $container->register('profiler', 'ProfilerClass'); $container->registerForAutoconfiguration(DataCollectorInterface::class)->addTag('data_collector'); - $container->register('mydatacollector', \get_class($dataCollector))->setAutoconfigured(true); + $container->register('mydatacollector', $dataCollector::class)->setAutoconfigured(true); (new ResolveInstanceofConditionalsPass())->process($container); (new ProfilerPass())->process($container); - $idForTemplate = \get_class($dataCollector); + $idForTemplate = $dataCollector::class; $this->assertSame(['mydatacollector' => [$idForTemplate, 'foo']], $container->getParameter('data_collector.templates')); // grab the method calls off of the "profiler" definition diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index c27a22fe85c19..ae3c349c91e6e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -383,19 +383,19 @@ public function testBusMiddlewareDontMerge() $this->assertEquals( [ 'existing_bus' => [ - 'default_middleware' => true, + 'default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => [ ['id' => 'existing_bus.middleware', 'arguments' => []], ], ], 'common_bus' => [ - 'default_middleware' => false, + 'default_middleware' => ['enabled' => false, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => [ ['id' => 'common_bus.new_middleware', 'arguments' => []], ], ], 'new_bus' => [ - 'default_middleware' => true, + 'default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => [ ['id' => 'new_bus.middleware', 'arguments' => []], ], @@ -532,6 +532,7 @@ protected static function getBundleDefaultConfig() 'https_port' => 443, 'strict_requirements' => true, 'utf8' => true, + 'cache_dir' => '%kernel.cache_dir%', ], 'session' => [ 'enabled' => false, @@ -606,7 +607,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor ], ], 'default_bus' => null, - 'buses' => ['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]], + 'buses' => ['messenger.bus.default' => ['default_middleware' => ['enabled' => true, 'allow_no_handlers' => false, 'allow_no_senders' => true], 'middleware' => []]], 'reset_on_message' => true, ], 'disallow_search_engine_index' => true, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_default_csrf.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_default_csrf.php index 0c491714a05a9..1f2556a5cfcbe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_default_csrf.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_default_csrf.php @@ -2,9 +2,6 @@ $container->loadFromExtension('framework', [ 'http_method_override' => false, - 'form' => [ - 'legacy_error_messages' => false, - ], 'session' => [ 'storage_factory_id' => 'session.storage.factory.native', 'handler_id' => null, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php index 8236fced45021..19f22f2c78c99 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php @@ -20,6 +20,7 @@ 'multiplier' => 3, 'max_delay' => 100, ], + 'rate_limiter' => 'customised_worker' ], 'failed' => 'in-memory:///', 'redis' => 'redis://127.0.0.1:6379/messages', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml index 97104d0571e52..53566a99ed449 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml @@ -8,7 +8,6 @@ - diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml index 55616c2d6a7ad..19245eb18c542 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml @@ -9,6 +9,5 @@ - diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml index cf88a085adc5e..d2147ae32db48 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml @@ -8,7 +8,6 @@ - diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_default_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_default_csrf.xml index c2700a6b20a76..f698e9aa8bb5f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_default_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_default_csrf.xml @@ -7,7 +7,7 @@ http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_no_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_no_csrf.xml index afb1869c5ab5d..facd62112d0e4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_no_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_no_csrf.xml @@ -7,7 +7,7 @@ http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index 00da2a6c0f963..b2f5473d7d7df 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -10,7 +10,7 @@ fr en - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml index d78c802810ae6..28e27e380bfe0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml @@ -10,7 +10,7 @@ - + Queue diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_default_csrf.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_default_csrf.yml index d6e9d77b93678..51a14623654be 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_default_csrf.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_default_csrf.yml @@ -1,7 +1,5 @@ framework: http_method_override: false - form: - legacy_error_messages: false session: storage_factory_id: session.storage.factory.native handler_id: null diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml index b16f9b6a8f09d..24471939c5435 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml @@ -18,6 +18,7 @@ framework: delay: 7 multiplier: 3 max_delay: 100 + rate_limiter: customised_worker failed: 'in-memory:///' redis: 'redis://127.0.0.1:6379/messages' beanstalkd: 'beanstalkd://127.0.0.1:11300' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 3743acfe915f9..640a152402bea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -59,7 +59,7 @@ use Symfony\Component\Notifier\ChatterInterface; use Symfony\Component\Notifier\TexterInterface; use Symfony\Component\PropertyAccess\PropertyAccessor; -use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Core\AuthenticationEvents; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; @@ -295,6 +295,8 @@ public function testWorkflows() $this->assertArrayHasKey('index_4', $args); $this->assertNull($args['index_4'], 'Workflows has eventsToDispatch=null'); + $this->assertSame(['workflow' => [['name' => 'article']], 'workflow.workflow' => [['name' => 'article']]], $container->getDefinition('workflow.article')->getTags()); + $this->assertTrue($container->hasDefinition('workflow.article.definition'), 'Workflow definition is registered as a service'); $workflowDefinition = $container->getDefinition('workflow.article.definition'); @@ -324,6 +326,8 @@ public function testWorkflows() $this->assertSame('state_machine.abstract', $container->getDefinition('state_machine.pull_request')->getParent()); $this->assertTrue($container->hasDefinition('state_machine.pull_request.definition'), 'State machine definition is registered as a service'); + $this->assertSame(['workflow' => [['name' => 'pull_request']], 'workflow.state_machine' => [['name' => 'pull_request']]], $container->getDefinition('state_machine.pull_request')->getTags()); + $stateMachineDefinition = $container->getDefinition('state_machine.pull_request.definition'); $this->assertSame( @@ -371,8 +375,8 @@ public function testWorkflows() $this->assertInstanceOf(Reference::class, $markingStoreRef); $this->assertEquals('workflow_service', (string) $markingStoreRef); - $this->assertTrue($container->hasDefinition('workflow.registry'), 'Workflow registry is registered as a service'); - $registryDefinition = $container->getDefinition('workflow.registry'); + $this->assertTrue($container->hasDefinition('.workflow.registry'), 'Workflow registry is registered as a service'); + $registryDefinition = $container->getDefinition('.workflow.registry'); $this->assertGreaterThan(0, \count($registryDefinition->getMethodCalls())); } @@ -957,6 +961,12 @@ public function testMessengerTransports() return array_shift($values); }, $failureTransports); $this->assertEquals($expectedTransportsByFailureTransports, $failureTransportsReferences); + + $rateLimitedTransports = $container->getDefinition('messenger.rate_limiter_locator')->getArgument(0); + $expectedRateLimitersByRateLimitedTransports = [ + 'customised' => new Reference('limiter.customised_worker'), + ]; + $this->assertEquals($expectedRateLimitersByRateLimitedTransports, $rateLimitedTransports); } public function testMessengerRouting() @@ -1003,8 +1013,8 @@ public function testMessengerWithMultipleBuses() ['id' => 'reject_redelivered_message_middleware'], ['id' => 'dispatch_after_current_bus'], ['id' => 'failed_message_processing_middleware'], - ['id' => 'send_message'], - ['id' => 'handle_message'], + ['id' => 'send_message', 'arguments' => [true]], + ['id' => 'handle_message', 'arguments' => [false]], ], $container->getParameter('messenger.bus.commands.middleware')); $this->assertTrue($container->has('messenger.bus.events')); $this->assertSame([], $container->getDefinition('messenger.bus.events')->getArgument(0)); @@ -1014,8 +1024,8 @@ public function testMessengerWithMultipleBuses() ['id' => 'dispatch_after_current_bus'], ['id' => 'failed_message_processing_middleware'], ['id' => 'with_factory', 'arguments' => ['foo', true, ['bar' => 'baz']]], - ['id' => 'send_message'], - ['id' => 'handle_message'], + ['id' => 'send_message', 'arguments' => [true]], + ['id' => 'handle_message', 'arguments' => [false]], ], $container->getParameter('messenger.bus.events.middleware')); $this->assertTrue($container->has('messenger.bus.queries')); $this->assertSame([], $container->getDefinition('messenger.bus.queries')->getArgument(0)); @@ -1076,7 +1086,7 @@ public function testTranslator() $files, '->registerTranslatorConfiguration() finds Form translation resources' ); - $ref = new \ReflectionClass(Security::class); + $ref = new \ReflectionClass(AuthenticationEvents::class); $this->assertContains( strtr(\dirname($ref->getFileName()).'/Resources/translations/security.en.xlf', '/', \DIRECTORY_SEPARATOR), $files, @@ -2270,26 +2280,14 @@ private function assertCachePoolServiceDefinitionIsCreated(ContainerBuilder $con $parentDefinition = $container->findDefinition($parentId); } while ($parentDefinition instanceof ChildDefinition); - switch ($adapter) { - case 'cache.adapter.apcu': - $this->assertSame(ApcuAdapter::class, $parentDefinition->getClass()); - break; - case 'cache.app': - case 'cache.adapter.filesystem': - $this->assertSame(FilesystemAdapter::class, $parentDefinition->getClass()); - break; - case 'cache.adapter.psr6': - $this->assertSame(ProxyAdapter::class, $parentDefinition->getClass()); - break; - case 'cache.adapter.redis': - $this->assertSame(RedisAdapter::class, $parentDefinition->getClass()); - break; - case 'cache.adapter.array': - $this->assertSame(ArrayAdapter::class, $parentDefinition->getClass()); - break; - default: - $this->fail('Unresolved adapter: '.$adapter); - } + match ($adapter) { + 'cache.adapter.apcu' => $this->assertSame(ApcuAdapter::class, $parentDefinition->getClass()), + 'cache.app', 'cache.adapter.filesystem' => $this->assertSame(FilesystemAdapter::class, $parentDefinition->getClass()), + 'cache.adapter.psr6' => $this->assertSame(ProxyAdapter::class, $parentDefinition->getClass()), + 'cache.adapter.redis' => $this->assertSame(RedisAdapter::class, $parentDefinition->getClass()), + 'cache.adapter.array' => $this->assertSame(ArrayAdapter::class, $parentDefinition->getClass()), + default => $this->fail('Unresolved adapter: '.$adapter), + }; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php index 9c9f12a010ed1..a904c4c86ee90 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php @@ -103,7 +103,7 @@ public function testRateLimiterWithLockFactory() $this->fail('No LogicException thrown'); } catch (LogicException $e) { - $this->assertEquals('Rate limiter "with_lock" requires the Lock component to be installed and configured.', $e->getMessage()); + $this->assertEquals('Rate limiter "with_lock" requires the Lock component to be configured.', $e->getMessage()); } $container = $this->createContainerFromClosure(function (ContainerBuilder $container) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/DeprecatedClass.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/DeprecatedClass.php new file mode 100644 index 0000000000000..70a400f6efdff --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/DeprecatedClass.php @@ -0,0 +1,10 @@ + - + + + alias_1 + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.json index 2fb039a9fa0f5..419ee67863813 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.json @@ -12,6 +12,7 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get", @@ -36,6 +37,9 @@ "name": "tag2", "parameters": [] } + ], + "usages": [ + ".alias_2" ] } ] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.md index 7894367566b87..d25978492e100 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.md @@ -13,6 +13,7 @@ - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` @@ -23,3 +24,4 @@ - Tag: `tag1` - Attr3: val3 - Tag: `tag2` +- Usages: .alias_2 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.txt index 4fd20cf7e5fab..6ab25c269fcd9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.txt @@ -22,4 +22,5 @@ Required File /path/to/file Factory Service factory.service Factory Method get + Usages .alias_2 ----------------- --------------------------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.xml index bbb7b92e44f8a..f9d5c70c80171 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.xml @@ -1,6 +1,6 @@ - + @@ -15,4 +15,7 @@ + + .alias_2 + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.json index 8835a17d6f46e..28d64c611753a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.json @@ -9,6 +9,7 @@ "abstract": true, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [ { "type": "service", @@ -24,12 +25,14 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [ "arg1", "arg2" ], "file": null, - "tags": [] + "tags": [], + "usages": [] }, [ "foo", @@ -46,9 +49,11 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [], "file": null, - "tags": [] + "tags": [], + "usages": [] } ], [ @@ -69,7 +74,8 @@ "file": null, "factory_class": "Full\\Qualified\\FactoryClass", "factory_method": "get", - "tags": [] + "tags": [], + "usages": [] }, "definition_without_class": { "class": "", @@ -80,9 +86,11 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [], "file": null, - "tags": [] + "tags": [], + "usages": [] }, "service_container": { "class": "Symfony\\Component\\DependencyInjection\\ContainerInterface", @@ -93,10 +101,12 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "description": "ContainerInterface is the interface implemented by service container classes.", "arguments": [], "file": null, - "tags": [] + "tags": [], + "usages": [] } }, "aliases": { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.md index 66d57cd84c340..57a209ecb95cc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.md @@ -14,9 +14,11 @@ Definitions - Abstract: yes - Autowired: no - Autoconfigured: no +- Deprecated: no - Arguments: yes - Factory Class: `Full\Qualified\FactoryClass` - Factory Method: `get` +- Usages: none ### definition_without_class @@ -28,7 +30,9 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - Arguments: no +- Usages: none ### service_container @@ -41,7 +45,9 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - Arguments: no +- Usages: none Aliases diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.xml index 3a51fa1646908..fdddad6537440 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.xml @@ -1,12 +1,12 @@ - + %parameter% - + arg1 arg2 @@ -15,7 +15,7 @@ foo - + @@ -24,8 +24,8 @@ placeholder - - + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json index 49e18b886836e..0d6198b07e3a2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json @@ -9,10 +9,12 @@ "abstract": true, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": null, "factory_class": "Full\\Qualified\\FactoryClass", "factory_method": "get", - "tags": [] + "tags": [], + "usages": [] }, "definition_without_class": { "class": "", @@ -23,8 +25,10 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": null, - "tags": [] + "tags": [], + "usages": [] }, "service_container": { "class": "Symfony\\Component\\DependencyInjection\\ContainerInterface", @@ -35,9 +39,11 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "description": "ContainerInterface is the interface implemented by service container classes.", "file": null, - "tags": [] + "tags": [], + "usages": [] } }, "aliases": { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md index b7daad45a8ed3..2532a2c4eea58 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md @@ -14,8 +14,10 @@ Definitions - Abstract: yes - Autowired: no - Autoconfigured: no +- Deprecated: no - Factory Class: `Full\Qualified\FactoryClass` - Factory Method: `get` +- Usages: none ### definition_without_class @@ -27,6 +29,8 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no +- Usages: none ### service_container @@ -39,6 +43,8 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no +- Usages: none Aliases diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml index 05f83391f6208..3b13b72643b76 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml @@ -1,11 +1,11 @@ - + - - + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json index 401c588c03d42..9bb716e442e17 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json @@ -9,6 +9,7 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get", @@ -33,7 +34,8 @@ "name": "tag2", "parameters": [] } - ] + ], + "usages": [] }, ".definition_3": { "class": "Full\\Qualified\\Class3", @@ -44,10 +46,12 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "factory_service": "inline factory service (Full\\Qualified\\FactoryClass)", "factory_method": "get", - "tags": [] + "tags": [], + "usages": [] } }, "aliases": { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md index d6daca9971dc7..3825ed8ebbba4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md @@ -14,6 +14,7 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` @@ -24,6 +25,7 @@ Definitions - Tag: `tag1` - Attr3: val3 - Tag: `tag2` +- Usages: none ### .definition_3 @@ -35,9 +37,11 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Factory Service: inline factory service (`Full\Qualified\FactoryClass`) - Factory Method: `get` +- Usages: none Aliases diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml index b9416fd069d05..d3cf16a0f4c5a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml @@ -1,7 +1,7 @@ - + @@ -17,7 +17,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json index caba59cd5ba47..66eb0af836225 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json @@ -9,6 +9,7 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get", @@ -33,7 +34,8 @@ "name": "tag2", "parameters": [] } - ] + ], + "usages": [] } }, "aliases": [], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md index a7a03bc391a55..a76b77df04e55 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md @@ -14,6 +14,7 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` @@ -24,3 +25,4 @@ Definitions - Tag: `tag1` - Attr3: val3 - Tag: `tag2` +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml index 6dd2fc6173b10..b2929b01ac403 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml @@ -1,6 +1,6 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json index 7f49ac241ffde..e0679f2cac58d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json @@ -9,12 +9,14 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get", "calls": [ "setMailer" - ] + ], + "usages": [] } ], "tag2": [ @@ -27,12 +29,14 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get", "calls": [ "setMailer" - ] + ], + "usages": [] } ] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md index 563ce2d2caf39..f9558d326e7b3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md @@ -14,10 +14,12 @@ tag1 - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` - Call: `setMailer` +- Usages: none tag2 @@ -33,7 +35,9 @@ tag2 - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` - Call: `setMailer` +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml index 77975ee27c639..75a9714f5d8a6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml @@ -1,7 +1,7 @@ - + @@ -9,7 +9,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.json index a9d9697847fdb..75d893297cd24 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.json @@ -9,6 +9,7 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "tags": [ { @@ -26,7 +27,8 @@ "priority": 0 } } - ] + ], + "usages": [] }, "definition_1": { "class": "Full\\Qualified\\Class1", @@ -37,6 +39,7 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get", @@ -61,7 +64,8 @@ "name": "tag2", "parameters": [] } - ] + ], + "usages": [] }, "definition_4": { "class": "Full\\Qualified\\Class4", @@ -72,6 +76,7 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "tags": [ { @@ -80,7 +85,8 @@ "priority": 0 } } - ] + ], + "usages": [] }, "definition_2": { "class": "Full\\Qualified\\Class2", @@ -91,6 +97,7 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "tags": [ { @@ -101,7 +108,8 @@ "priority": -20 } } - ] + ], + "usages": [] } }, "aliases": [], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.md index 8c0fef6aa3bea..7137e1b1d81d1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.md @@ -14,6 +14,7 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Tag: `tag1` - Attr3: val3 @@ -22,6 +23,7 @@ Definitions - Attr1: val1 - Attr2: val2 - Priority: 0 +- Usages: none ### definition_1 @@ -33,6 +35,7 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` @@ -43,6 +46,7 @@ Definitions - Tag: `tag1` - Attr2: val2 - Tag: `tag2` +- Usages: none ### definition_4 @@ -54,9 +58,11 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Tag: `tag1` - Priority: 0 +- Usages: none ### definition_2 @@ -68,8 +74,10 @@ Definitions - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Tag: `tag1` - Attr1: val1 - Attr2: val2 - Priority: -20 +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.xml index 2e00c99955257..4bbdbfe5f3f44 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.xml @@ -1,6 +1,6 @@ - + val3 @@ -13,7 +13,7 @@ - + @@ -29,14 +29,14 @@ - + 0 - + val1 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json index 940bab12699b1..735b3df470887 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json @@ -7,8 +7,10 @@ "abstract": true, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": null, "factory_class": "Full\\Qualified\\FactoryClass", "factory_method": "get", - "tags": [] + "tags": [], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md index e4d0429b9087d..c7ad62954ebc3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md @@ -6,5 +6,7 @@ - Abstract: yes - Autowired: no - Autoconfigured: no +- Deprecated: no - Factory Class: `Full\Qualified\FactoryClass` - Factory Method: `get` +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt index 88cf4cd553aef..8ec7be868ca65 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt @@ -13,5 +13,6 @@ Autoconfigured no Factory Class Full\Qualified\FactoryClass Factory Method get + Usages none ---------------- ----------------------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml index d5015fdde3bd7..be2b16b57ffa7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml @@ -1,4 +1,4 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json index 1c7b1ae14af42..622904da2ef86 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json @@ -7,6 +7,7 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get", @@ -31,5 +32,6 @@ "name": "tag2", "parameters": [] } - ] + ], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md index 6ff6d60b3dc93..376cbdb52348b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md @@ -6,6 +6,7 @@ - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` @@ -16,3 +17,4 @@ - Tag: `tag1` - Attr3: val3 - Tag: `tag2` +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt index 0ceb807a45c2f..2b8d3dde599e0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt @@ -17,5 +17,6 @@ Required File /path/to/file Factory Service factory.service Factory Method get + Usages none ----------------- --------------------------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml index 72319eca97a4c..ab072f3e3adf5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml @@ -1,5 +1,5 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.json index 4bf56746493f8..11768d0de1a45 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.json @@ -7,8 +7,10 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": "\/path\/to\/file", "factory_service": "inline factory service (Full\\Qualified\\FactoryClass)", "factory_method": "get", - "tags": [] + "tags": [], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.md index 68f51634db99f..8a9651641d747 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.md @@ -6,6 +6,8 @@ - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - File: `/path/to/file` - Factory Service: inline factory service (`Full\Qualified\FactoryClass`) - Factory Method: `get` +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.txt index 35ddaf3e452a8..63cd01b592f51 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.txt @@ -14,5 +14,6 @@ Required File /path/to/file Factory Service inline factory service (Full\Qualified\FactoryClass) Factory Method get + Usages none ----------------- ------------------------------------------------------ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.xml index e81c77014253f..ddd7dfb38ed26 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.xml @@ -1,4 +1,4 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.json index 769679c6d23f5..b0a612030cae1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.json @@ -7,6 +7,7 @@ "abstract": true, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [ { "type": "service", @@ -22,12 +23,14 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [ "arg1", "arg2" ], "file": null, - "tags": [] + "tags": [], + "usages": [] }, [ "foo", @@ -44,9 +47,11 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [], "file": null, - "tags": [] + "tags": [], + "usages": [] } ], [ @@ -67,5 +72,6 @@ "file": null, "factory_class": "Full\\Qualified\\FactoryClass", "factory_method": "get", - "tags": [] + "tags": [], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.md index 7cee17ad9cbb4..b99162bbf439d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.md @@ -6,6 +6,8 @@ - Abstract: yes - Autowired: no - Autoconfigured: no +- Deprecated: no - Arguments: yes - Factory Class: `Full\Qualified\FactoryClass` -- Factory Method: `get` \ No newline at end of file +- Factory Method: `get` +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.txt index 635fadc7d8742..237b018cd25bb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.txt @@ -21,5 +21,6 @@ - Service(definition_1) - Service(.definition_2) Abstract argument (placeholder) + Usages none ---------------- --------------------------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.xml index b71fe8e144b68..eba7e7bbdcd7f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.xml @@ -1,10 +1,10 @@ - + %parameter% - + arg1 arg2 @@ -13,7 +13,7 @@ foo - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.json index 8f65d27b83ea9..20ef01a34b7a5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.json @@ -7,6 +7,7 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [], "file": "\/path\/to\/file", "factory_service": "factory.service", @@ -32,5 +33,6 @@ "name": "tag2", "parameters": [] } - ] + ], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.md index e9f0efbd10bea..f5bf50e61764c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.md @@ -6,6 +6,7 @@ - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - Arguments: no - File: `/path/to/file` - Factory Service: `factory.service` @@ -17,3 +18,4 @@ - Tag: `tag1` - Attr3: val3 - Tag: `tag2` +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.txt index 0ceb807a45c2f..2b8d3dde599e0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.txt @@ -17,5 +17,6 @@ Required File /path/to/file Factory Service factory.service Factory Method get + Usages none ----------------- --------------------------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.xml index 72319eca97a4c..ab072f3e3adf5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_2.xml @@ -1,5 +1,5 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.json index 94c2fda5402fc..c96c06d63ad0e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.json @@ -7,9 +7,11 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [], "file": "\/path\/to\/file", "factory_service": "inline factory service (Full\\Qualified\\FactoryClass)", "factory_method": "get", - "tags": [] + "tags": [], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.md index 2ce1f264dfc6c..5bfafe3d0e28a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.md @@ -6,7 +6,9 @@ - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - Arguments: no - File: `/path/to/file` - Factory Service: inline factory service (`Full\Qualified\FactoryClass`) - Factory Method: `get` +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.txt index 6e400de44e8ff..cfde9dea018ec 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.txt @@ -14,5 +14,6 @@ Required File /path/to/file Factory Service inline factory service (Full\Qualified\FactoryClass) Factory Method get + Usages none ----------------- ------------------------------------------------------ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.xml index e81c77014253f..ddd7dfb38ed26 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.xml @@ -1,4 +1,4 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.json index d5580ee3334a4..08f69f738c9ef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.json @@ -7,9 +7,11 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [ "Symfony\\Bundle\\FrameworkBundle\\Tests\\Fixtures\\FooUnitEnum::FOO" ], "file": null, - "tags": [] + "tags": [], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.md index 78ef17f14b1fa..794c9710fc994 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.md @@ -6,4 +6,6 @@ - Abstract: no - Autowired: no - Autoconfigured: no -- Arguments: yes \ No newline at end of file +- Deprecated: no +- Arguments: yes +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.txt index 9556f9b832f4e..c0768306c8a0c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.txt @@ -12,5 +12,6 @@ Autowired no Autoconfigured no Arguments Symfony\Bundle\FrameworkBundle\Tests\Fixtures\FooUnitEnum::FOO + Usages none ---------------- ---------------------------------------------------------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.xml index cb58a6d935e97..7a555ae165b5e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_with_enum.xml @@ -1,4 +1,4 @@ - + Symfony\Bundle\FrameworkBundle\Tests\Fixtures\FooUnitEnum::FOO diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.json index 90a6b5dece406..c1305ac0c56c1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.json @@ -7,7 +7,9 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "arguments": [], "file": null, - "tags": [] + "tags": [], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.md index 217c965b67a31..7c7bad74dcf06 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.md @@ -6,4 +6,6 @@ - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no - Arguments: no +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.txt index ed49579037fda..186639de02b25 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.txt @@ -11,5 +11,6 @@ Abstract no Autowired no Autoconfigured no + Usages none ---------------- ------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.xml index ee6df8928e245..2ebeaf8dfcb4c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_without_class.xml @@ -1,2 +1,2 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.json index eda84251babd5..078f7cdca6b4b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.json @@ -7,6 +7,8 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": null, - "tags": [] + "tags": [], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.md index ca50a70f77111..be221535f9889 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.md @@ -6,3 +6,5 @@ - Abstract: no - Autowired: no - Autoconfigured: no +- Deprecated: no +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.txt index ed49579037fda..186639de02b25 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.txt @@ -11,5 +11,6 @@ Abstract no Autowired no Autoconfigured no + Usages none ---------------- ------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.xml index ee6df8928e245..2ebeaf8dfcb4c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_without_class.xml @@ -1,2 +1,2 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.json index f277359409e35..c6de89ce5cd94 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.json @@ -7,7 +7,9 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "description": "This is a class with a doc comment.", "file": null, - "tags": [] + "tags": [], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.md index 93f683004fcd5..132147324bceb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.md @@ -7,4 +7,5 @@ - Abstract: no - Autowired: no - Autoconfigured: no - +- Deprecated: no +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.txt index b5ad3a228f66e..b04c636cd9af3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.txt @@ -13,5 +13,6 @@ This is a class with a doc comment. Abstract no Autowired no Autoconfigured no + Usages none ---------------- ----------------------------------------------------------------------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.xml index 2444a20efb7a8..b4ead6579d5ba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_1.xml @@ -1,4 +1,4 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.json index dd47edc6df70c..7b387fd8683c1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.json @@ -7,6 +7,8 @@ "abstract": false, "autowire": false, "autoconfigure": false, + "deprecated": false, "file": null, - "tags": [] + "tags": [], + "usages": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.md index 13226df00c55b..0526ba117ecaa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.md @@ -6,4 +6,5 @@ - Abstract: no - Autowired: no - Autoconfigured: no - +- Deprecated: no +- Usages: none diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.txt index ce9c6a4b5a208..33e2379a7d556 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.txt @@ -11,5 +11,6 @@ Abstract no Autowired no Autoconfigured no + Usages none ---------------- -------------------------------------------------------------------------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.xml index 7a8d9dbe6b177..045787e5c6246 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/existing_class_def_2.xml @@ -1,2 +1,2 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractAttributeRoutingTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractAttributeRoutingTest.php new file mode 100644 index 0000000000000..cf971d0d60dc3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractAttributeRoutingTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +abstract class AbstractAttributeRoutingTest extends AbstractWebTestCase +{ + /** + * @dataProvider getRoutes + */ + public function testAnnotatedController(string $path, string $expectedValue) + { + $client = $this->createClient(['test_case' => $this->getTestCaseApp(), 'root_config' => 'config.yml']); + $client->request('GET', '/annotated'.$path); + + $this->assertSame(200, $client->getResponse()->getStatusCode()); + $this->assertSame($expectedValue, $client->getResponse()->getContent()); + + $router = self::getContainer()->get('router'); + + $this->assertSame('/annotated/create-transaction', $router->generate('symfony_framework_tests_functional_test_annotated_createtransaction')); + } + + public function getRoutes(): array + { + return [ + ['/null_request', 'Symfony\Component\HttpFoundation\Request'], + ['/null_argument', ''], + ['/null_argument_with_route_param', ''], + ['/null_argument_with_route_param/value', 'value'], + ['/argument_with_route_param_and_default', 'value'], + ['/argument_with_route_param_and_default/custom', 'custom'], + ]; + } + + abstract protected function getTestCaseApp(): string; +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AnnotatedControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AnnotatedControllerTest.php index a0be5fcef06a7..631d75bb50dff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AnnotatedControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AnnotatedControllerTest.php @@ -11,33 +11,10 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; -class AnnotatedControllerTest extends AbstractWebTestCase +class AnnotatedControllerTest extends AbstractAttributeRoutingTest { - /** - * @dataProvider getRoutes - */ - public function testAnnotatedController($path, $expectedValue) + protected function getTestCaseApp(): string { - $client = $this->createClient(['test_case' => 'AnnotatedController', 'root_config' => 'config.yml']); - $client->request('GET', '/annotated'.$path); - - $this->assertSame(200, $client->getResponse()->getStatusCode()); - $this->assertSame($expectedValue, $client->getResponse()->getContent()); - - $router = self::getContainer()->get('router'); - - $this->assertSame('/annotated/create-transaction', $router->generate('symfony_framework_tests_functional_test_annotated_createtransaction')); - } - - public function getRoutes() - { - return [ - ['/null_request', 'Symfony\Component\HttpFoundation\Request'], - ['/null_argument', ''], - ['/null_argument_with_route_param', ''], - ['/null_argument_with_route_param/value', 'value'], - ['/argument_with_route_param_and_default', 'value'], - ['/argument_with_route_param_and_default/custom', 'custom'], - ]; + return 'AnnotatedController'; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/AnnotatedController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/AnnotatedController.php index f2f077786f2b7..2bee3d0b833e1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/AnnotatedController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/AnnotatedController.php @@ -17,42 +17,32 @@ class AnnotatedController { - /** - * @Route("/null_request", name="null_request") - */ - public function requestDefaultNullAction(Request $request = null) + #[Route('/null_request', name: 'null_request')] + public function requestDefaultNullAction(Request $request = null): Response { - return new Response($request ? \get_class($request) : null); + return new Response($request ? $request::class : null); } - /** - * @Route("/null_argument", name="null_argument") - */ - public function argumentDefaultNullWithoutRouteParamAction($value = null) + #[Route('/null_argument', name: 'null_argument')] + public function argumentDefaultNullWithoutRouteParamAction($value = null): Response { return new Response($value); } - /** - * @Route("/null_argument_with_route_param/{value}", name="null_argument_with_route_param") - */ - public function argumentDefaultNullWithRouteParamAction($value = null) + #[Route('/null_argument_with_route_param/{value}', name: 'null_argument_with_route_param')] + public function argumentDefaultNullWithRouteParamAction($value = null): Response { return new Response($value); } - /** - * @Route("/argument_with_route_param_and_default/{value}", defaults={"value": "value"}, name="argument_with_route_param_and_default") - */ - public function argumentWithoutDefaultWithRouteParamAndDefaultAction($value) + #[Route('/argument_with_route_param_and_default/{value}', defaults: ['value' => 'value'], name: 'argument_with_route_param_and_default')] + public function argumentWithoutDefaultWithRouteParamAndDefaultAction($value): Response { return new Response($value); } - /** - * @Route("/create-transaction") - */ - public function createTransaction() + #[Route('/create-transaction')] + public function createTransaction(): Response { return new Response(); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/EmailController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/EmailController.php index 1a871f79be907..4f479138cb073 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/EmailController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/EmailController.php @@ -15,6 +15,7 @@ use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Part\DataPart; class EmailController { @@ -25,7 +26,7 @@ public function indexAction(MailerInterface $mailer) ->addCc('cc@symfony.com') ->text('Bar!') ->html('

Foo

') - ->attach(file_get_contents(__FILE__), 'foobar.php') + ->addPart(new DataPart(file_get_contents(__FILE__), 'foobar.php')) ); $mailer->send((new Email())->to('fabien@symfony.com', 'thomas@symfony.com')->from('fabien@symfony.com')->subject('Foo') @@ -33,7 +34,7 @@ public function indexAction(MailerInterface $mailer) ->addCc('cc@symfony.com') ->text('Bar!') ->html('

Foo

') - ->attach(file_get_contents(__FILE__), 'foobar.php') + ->addPart(new DataPart(file_get_contents(__FILE__), 'foobar.php')) ); return new Response(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/NotificationController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/NotificationController.php new file mode 100644 index 0000000000000..0cdb47c20f40a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/NotificationController.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Notifier\Notification\Notification; +use Symfony\Component\Notifier\NotifierInterface; + +final class NotificationController +{ + public function indexAction(NotifierInterface $notifier) + { + $firstNotification = new Notification('Hello World!', ['chat/slack']); + $firstNotification->content('Symfony is awesome!'); + + $notifier->send($firstNotification); + + $secondNotification = (new Notification('New urgent notification')) + ->importance(Notification::IMPORTANCE_URGENT) + ; + $notifier->send($secondNotification); + + return new Response(); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php index d90d7512f24aa..28e3d141aaedc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php @@ -30,9 +30,6 @@ public function profileAction() return new Response('Welcome '.$this->container->get('security.token_storage')->getToken()->getUserIdentifier().'!'); } - /** - * {@inheritdoc} - */ public static function getSubscribedServices(): array { return [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TestExtension.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TestExtension.php index 2d88510520531..80cee0f1ae344 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TestExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TestExtension.php @@ -21,9 +21,6 @@ class TestExtension extends Extension implements PrependExtensionInterface { private $customConfig; - /** - * {@inheritdoc} - */ public function load(array $configs, ContainerBuilder $container) { $configuration = $this->getConfiguration($configs, $container); @@ -32,17 +29,11 @@ public function load(array $configs, ContainerBuilder $container) $container->setAlias('test.annotation_reader', new Alias('annotation_reader', true)); } - /** - * {@inheritdoc} - */ public function prepend(ContainerBuilder $container) { $container->prependExtensionConfig('test', ['custom' => 'foo']); } - /** - * {@inheritdoc} - */ public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface { return new Configuration($this->customConfig); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml index bfd37826b268b..5630ed621048f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml @@ -64,3 +64,7 @@ send_email: uid: resource: "../../Controller/UidController.php" type: "annotation" + +send_notification: + path: /send_notification + defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\NotificationController::indexAction } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php index 463318af39f47..09b99f82f7c64 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php @@ -60,6 +60,18 @@ public function testParametersValuesAreResolved() $this->assertStringContainsString('secret: test', $tester->getDisplay()); } + public function testParametersValuesAreFullyResolved() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(['name' => 'framework', '--resolve-env' => true]); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); + $this->assertStringContainsString('locale: en', $tester->getDisplay()); + $this->assertStringContainsString('secret: test', $tester->getDisplay()); + $this->assertStringContainsString('cookie_httponly: true', $tester->getDisplay()); + $this->assertStringContainsString('ide: null', $tester->getDisplay()); + } + public function testDefaultParameterValueIsResolvedIfConfigIsExisting() { $tester = $this->createCommandTester(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php index ee56012d307ca..949c6191a1252 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php @@ -69,6 +69,22 @@ public function testPrivateAlias() $this->assertStringContainsString('The "private_alias" service or alias has been removed', $tester->getDisplay()); } + public function testDeprecatedServiceAndAlias() + { + static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml']); + + $application = new Application(static::$kernel); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + + $tester->run(['command' => 'debug:container', 'name' => 'deprecated', '--format' => 'txt']); + $this->assertStringContainsString('[WARNING] The "deprecated" service is deprecated since foo/bar 1.9 and will be removed in 2.0', $tester->getDisplay()); + + $tester->run(['command' => 'debug:container', 'name' => 'deprecated_alias', '--format' => 'txt']); + $this->assertStringContainsString('[WARNING] The "deprecated_alias" alias is deprecated since foo/bar 1.9 and will be removed in 2.0', $tester->getDisplay()); + } + /** * @dataProvider provideIgnoreBackslashWhenFindingService */ @@ -84,6 +100,27 @@ public function testIgnoreBackslashWhenFindingService(string $validServiceId) $this->assertStringNotContainsString('No services found', $tester->getDisplay()); } + public function testTagsPartialSearch() + { + static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml']); + + $application = new Application(static::$kernel); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->setInputs(['0']); + $tester->run(['command' => 'debug:container', '--tag' => 'kernel.'], ['decorated' => false]); + + $this->assertStringContainsString('Select one of the following tags to display its information', $tester->getDisplay()); + $this->assertStringContainsString('[0] kernel.event_subscriber', $tester->getDisplay()); + $this->assertStringContainsString('[1] kernel.locale_aware', $tester->getDisplay()); + $this->assertStringContainsString('[2] kernel.cache_warmer', $tester->getDisplay()); + $this->assertStringContainsString('[3] kernel.fragment_renderer', $tester->getDisplay()); + $this->assertStringContainsString('[4] kernel.reset', $tester->getDisplay()); + $this->assertStringContainsString('[5] kernel.cache_clearer', $tester->getDisplay()); + $this->assertStringContainsString('Symfony Container Services Tagged with "kernel.event_subscriber" Tag', $tester->getDisplay()); + } + public function testDescribeEnvVars() { putenv('REAL=value'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/NotificationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/NotificationTest.php new file mode 100644 index 0000000000000..c11211b02b675 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/NotificationTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +final class NotificationTest extends AbstractWebTestCase +{ + /** + * @requires function \Symfony\Bundle\MercureBundle\MercureBundle::build + */ + public function testNotifierAssertion() + { + $client = $this->createClient(['test_case' => 'Notifier', 'root_config' => 'config.yml', 'debug' => true]); + $client->request('GET', '/send_notification'); + + $this->assertNotificationCount(2); + $first = 0; + $second = 1; + $this->assertNotificationIsNotQueued($this->getNotifierEvent($first)); + $this->assertNotificationIsNotQueued($this->getNotifierEvent($second)); + + $notification = $this->getNotifierMessage($first); + $this->assertNotificationSubjectContains($notification, 'Hello World!'); + $this->assertNotificationSubjectNotContains($notification, 'New urgent notification'); + $this->assertNotificationTransportIsEqual($notification, 'slack'); + $this->assertNotificationTransportIsNotEqual($notification, 'mercure'); + + $notification = $this->getNotifierMessage($second); + $this->assertNotificationSubjectContains($notification, 'New urgent notification'); + $this->assertNotificationSubjectNotContains($notification, 'Hello World!'); + $this->assertNotificationTransportIsEqual($notification, 'mercure'); + $this->assertNotificationTransportIsNotEqual($notification, 'slack'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Psr4RoutingTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Psr4RoutingTest.php new file mode 100644 index 0000000000000..0d0d715c9d0b4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Psr4RoutingTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +/** + * @requires function Symfony\Component\Routing\Loader\Psr4DirectoryLoader::__construct + */ +final class Psr4RoutingTest extends AbstractAttributeRoutingTest +{ + protected function getTestCaseApp(): string + { + return 'Psr4Routing'; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/UidTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/UidTest.php index 61ba430b9b3ea..da0b1e4fc80d1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/UidTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/UidTest.php @@ -11,15 +11,11 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; -use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\UidController; use Symfony\Component\Uid\Ulid; use Symfony\Component\Uid\UuidV1; use Symfony\Component\Uid\UuidV4; use Symfony\Component\Uid\UuidV6; -/** - * @see UidController - */ class UidTest extends AbstractWebTestCase { protected function setUp(): void @@ -31,11 +27,12 @@ protected function setUp(): void public function testArgumentValueResolverDisabled() { + $client = $this->createClient(['test_case' => 'Uid', 'root_config' => 'config_disabled.yml']); + $client->catchExceptions(false); + $this->expectException(\TypeError::class); $this->expectExceptionMessage('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\UidController::anyFormat(): Argument #1 ($userId) must be of type Symfony\Component\Uid\UuidV1, string given'); - $client = $this->createClient(['test_case' => 'Uid', 'root_config' => 'config_disabled.yml']); - $client->request('GET', '/1/uuid-v1/'.new UuidV1()); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml index 25c1c784298b5..cc1a01bb8f0b5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml @@ -11,6 +11,18 @@ services: private_alias: alias: public public: false + deprecated: + class: Symfony\Bundle\FrameworkBundle\Tests\Fixtures\DeprecatedClass + deprecated: + package: 'foo/bar' + version: '1.9' + message: The "%service_id%" service is deprecated since foo/bar 1.9 and will be removed in 2.0 + deprecated_alias: + alias: deprecated + deprecated: + package: 'foo/bar' + version: '1.9' + message: The "%alias_id%" alias is deprecated since foo/bar 1.9 and will be removed in 2.0 Symfony\Bundle\FrameworkBundle\Tests\Fixtures\BackslashClass: class: Symfony\Bundle\FrameworkBundle\Tests\Fixtures\BackslashClass Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ClassAliasExampleClass: '@public' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/bundles.php new file mode 100644 index 0000000000000..d50fdea7f582a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/bundles.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle; +use Symfony\Bundle\MercureBundle\MercureBundle; + +return [ + new FrameworkBundle(), + new TestBundle(), + new MercureBundle(), +]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/config.yml new file mode 100644 index 0000000000000..b8f28199a1fb4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/config.yml @@ -0,0 +1,25 @@ +imports: + - { resource: ../config/default.yml } + - { resource: services.yml } + +framework: + mailer: + dsn: 'null://null' + notifier: + chatter_transports: + slack: 'null://null' + mercure: 'null://null' + channel_policy: + urgent: ['chat/mercure'] + admin_recipients: + - { email: admin@example.com } + profiler: ~ + +mercure: + hubs: + default: + url: 'null://null' + jwt: + secret: '!ChangeMe!' + publish: [ 'foo', 'https://example.com/foo' ] + subscribe: [ 'bar', 'https://example.com/bar' ] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/routing.yml new file mode 100644 index 0000000000000..bd55884d56502 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/routing.yml @@ -0,0 +1,2 @@ +_notifiertest_bundle: + resource: '@TestBundle/Resources/config/routing.yml' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/services.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/services.yml new file mode 100644 index 0000000000000..d3a7586686058 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Notifier/services.yml @@ -0,0 +1,7 @@ +services: + _defaults: + public: true + + Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\NotificationController: + tags: ['controller.service_arguments'] + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/bundles.php new file mode 100644 index 0000000000000..15ff182c6fed5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/bundles.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle; + +return [ + new FrameworkBundle(), + new TestBundle(), +]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/config.yml new file mode 100644 index 0000000000000..377d3e7852064 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/config.yml @@ -0,0 +1,2 @@ +imports: + - { resource: ../config/default.yml } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/routing.yml new file mode 100644 index 0000000000000..d1dbf4abe9d5c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Psr4Routing/routing.yml @@ -0,0 +1,6 @@ +test_bundle: + prefix: /annotated + resource: + path: "@TestBundle/Controller" + namespace: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller + type: attribute diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml index ff27b54bcf8d3..a223672eb9e12 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml @@ -8,8 +8,6 @@ services: - container.service_subscriber security: - enable_authenticator_manager: true - providers: main: memory: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/ConcreteMicroKernel.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/ConcreteMicroKernel.php index 36ba1df088a7a..def880b2319b2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/ConcreteMicroKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/ConcreteMicroKernel.php @@ -92,9 +92,6 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load $c->register('halloween', 'stdClass')->setPublic(true); } - /** - * {@inheritdoc} - */ public static function getSubscribedEvents(): array { return [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php index 76b02674e90a3..b3f8e274ec9e5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php @@ -128,7 +128,7 @@ protected function configureContainer(ContainerConfigurator $c): void protected function configureRoutes(RoutingConfigurator $routes): void { - $routes->add('hello', '/')->controller([$this, 'helloAction']); + $routes->add('hello', '/')->controller($this->helloAction(...)); } }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/flex-style/src/FlexStyleMicroKernel.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/flex-style/src/FlexStyleMicroKernel.php index d3aaeb6d387fc..bf529a580489a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/flex-style/src/FlexStyleMicroKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/flex-style/src/FlexStyleMicroKernel.php @@ -100,6 +100,9 @@ protected function configureContainer(ContainerConfigurator $c): void ->factory([$this, 'createHalloween']) ->arg('$halloween', '%halloween%'); - $c->extension('framework', ['http_method_override' => false, 'router' => ['utf8' => true]]); + $c->extension('framework', [ + 'http_method_override' => false, + 'router' => ['utf8' => true], + ]); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php index ff0981a71444c..ab5074e8dfa45 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php @@ -418,9 +418,6 @@ private function createTranslator($loader, $options, $translatorClass = Translat class TranslatorWithInvalidLocale extends Translator { - /** - * {@inheritdoc} - */ public function getLocale(): string { return 'invalid locale'; diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php index b4d9a664a5e02..3116080f042c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php @@ -95,8 +95,6 @@ public function __construct(ContainerInterface $container, MessageFormatterInter } /** - * {@inheritdoc} - * * @return string[] */ public function warmUp(string $cacheDir): array @@ -128,9 +126,6 @@ public function addResource(string $format, mixed $resource, string $locale, str $this->resources[] = [$format, $resource, $locale, $domain]; } - /** - * {@inheritdoc} - */ protected function initializeCatalogue(string $locale) { $this->initialize(); diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index dc1e717e28595..4ab6b2e919e32 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -21,12 +21,12 @@ "ext-xml": "*", "symfony/cache": "^5.4|^6.0", "symfony/config": "^6.1", - "symfony/dependency-injection": "^6.1", + "symfony/dependency-injection": "^6.2", "symfony/deprecation-contracts": "^2.1|^3", "symfony/error-handler": "^6.1", "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^6.1", + "symfony/http-foundation": "^6.2", + "symfony/http-kernel": "^6.2", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", @@ -48,8 +48,8 @@ "symfony/http-client": "^5.4|^6.0", "symfony/lock": "^5.4|^6.0", "symfony/mailer": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", + "symfony/messenger": "^6.2", + "symfony/mime": "^6.2", "symfony/notifier": "^5.4|^6.0", "symfony/process": "^5.4|^6.0", "symfony/rate-limiter": "^5.4|^6.0", @@ -83,8 +83,8 @@ "symfony/form": "<5.4", "symfony/lock": "<5.4", "symfony/mailer": "<5.4", - "symfony/messenger": "<5.4", - "symfony/mime": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", "symfony/property-info": "<5.4", "symfony/property-access": "<5.4", "symfony/serializer": "<6.1", diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 35631064bacf3..dcefe374dda4c 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,10 +1,25 @@ CHANGELOG ========= +6.2 +--- + + * Add the `Security` helper class + * Deprecate the `Symfony\Component\Security\Core\Security` service alias, use `Symfony\Bundle\SecurityBundle\Security` instead + * Add `Security::getFirewallConfig()` to help to get the firewall configuration associated to the Request + * Add `Security::login()` to login programmatically + * Add `Security::logout()` to logout programmatically + * Add `security.firewalls.logout.enable_csrf` to enable CSRF protection using the default CSRF token generator + * Add RFC6750 Access Token support to allow token-based authentication + * Add `security.firewalls.switch_user.target_route` option to configure redirect target route on switch user + * Deprecate the `security.enable_authenticator_manager` config option + 6.1 --- * The `security.access_control` now accepts a `RequestMatcherInterface` under the `request_matcher` option as scope configuration + * The `security.access_control` now accepts an `attributes` array to match request attributes in the `RequestMatcher` + * The `security.access_control` now accepts a `route` option to match request route in the `RequestMatcher` * Display the inherited roles of the logged-in user in the Web Debug Toolbar 6.0 diff --git a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php index ea1e44018ea05..757f61629f6f5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php @@ -218,7 +218,7 @@ private function displayAuthenticators(string $name, SymfonyStyle $io): void array_map( static function ($authenticator) { return [ - \get_class($authenticator), + $authenticator::class, ]; }, $authenticators @@ -253,7 +253,7 @@ private function formatCallable(mixed $callable): string } if (method_exists($callable, '__invoke')) { - return sprintf('%s::__invoke()', \get_class($callable)); + return sprintf('%s::__invoke()', $callable::class); } throw new \InvalidArgumentException('Callable is not describable.'); diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index c7df65486efdf..96730d041c5fc 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -55,9 +55,6 @@ public function __construct(TokenStorageInterface $tokenStorage = null, RoleHier $this->hasVarDumper = class_exists(ClassStub::class); } - /** - * {@inheritdoc} - */ public function collect(Request $request, Response $response, \Throwable $exception = null) { if (null === $this->tokenStorage) { @@ -122,7 +119,7 @@ public function collect(Request $request, Response $response, \Throwable $except 'impersonator_user' => $impersonatorUser, 'impersonation_exit_path' => null, 'token' => $token, - 'token_class' => $this->hasVarDumper ? new ClassStub(\get_class($token)) : \get_class($token), + 'token_class' => $this->hasVarDumper ? new ClassStub($token::class) : $token::class, 'logout_url' => $logoutUrl, 'user' => $token->getUserIdentifier(), 'roles' => $assignedRoles, @@ -140,7 +137,7 @@ public function collect(Request $request, Response $response, \Throwable $except $voter = $voter->getDecoratedVoter(); } - $this->data['voters'][] = $this->hasVarDumper ? new ClassStub(\get_class($voter)) : \get_class($voter); + $this->data['voters'][] = $this->hasVarDumper ? new ClassStub($voter::class) : $voter::class; } // collect voter details @@ -205,9 +202,6 @@ public function collect(Request $request, Response $response, \Throwable $except $this->data['authenticators'] = $this->firewall ? $this->firewall->getAuthenticatorsInfo() : []; } - /** - * {@inheritdoc} - */ public function reset() { $this->data = []; @@ -350,9 +344,6 @@ public function getAuthenticators(): array|Data return $this->data['authenticators']; } - /** - * {@inheritdoc} - */ public function getName(): string { return 'security'; diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php index 5a3d0a1c609d8..a30900a5342e2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php @@ -38,9 +38,6 @@ public function supports(Request $request): ?bool return $this->listener->supports($request); } - /** - * {@inheritdoc} - */ public function authenticate(RequestEvent $event) { $startTime = microtime(true); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php index 0393e6c616b79..607d1f06c927d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php @@ -22,9 +22,6 @@ */ class AddExpressionLanguageProvidersPass implements CompilerPassInterface { - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { if ($container->has('security.expression_language')) { @@ -36,6 +33,7 @@ public function process(ContainerBuilder $container) if (!$container->hasDefinition('cache.system')) { $container->removeDefinition('cache.security_expression_language'); + $container->removeDefinition('cache.security_is_granted_attribute_expression_language'); } } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php index c3c7b86ab2f72..273a0db67fddf 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php @@ -29,9 +29,6 @@ class AddSecurityVotersPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('security.access.decision_manager')) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php index 461a5ad66d081..5b107b5ef0dfc 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php @@ -21,9 +21,6 @@ */ class AddSessionDomainConstraintPass implements CompilerPassInterface { - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { if (!$container->hasParameter('session.storage.options') || !$container->has('security.http_utils')) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php index d959d4bda9e67..fb4b6f83dc3b8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/CleanRememberMeVerifierPass.php @@ -21,9 +21,6 @@ */ class CleanRememberMeVerifierPass implements CompilerPassInterface { - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('cache.system')) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php index 20094957cdb65..8eac2cf83a8c0 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPass.php @@ -52,9 +52,6 @@ class RegisterGlobalSecurityEventListenersPass implements CompilerPassInterface SecurityEvents::INTERACTIVE_LOGIN, ]; - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { if (!$container->has('event_dispatcher') || !$container->hasParameter('security.firewalls')) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php index 2b1159eff979a..fdffab1bbf395 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php @@ -27,9 +27,6 @@ */ class RegisterTokenUsageTrackingPass implements CompilerPassInterface { - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { if (!$container->has('security.untracked_token_storage')) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/ReplaceDecoratedRememberMeHandlerPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/ReplaceDecoratedRememberMeHandlerPass.php index 5de431c2c04c8..4727e62f7c8ff 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/ReplaceDecoratedRememberMeHandlerPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/ReplaceDecoratedRememberMeHandlerPass.php @@ -26,9 +26,6 @@ final class ReplaceDecoratedRememberMeHandlerPass implements CompilerPassInterfa { private const HANDLER_TAG = 'security.remember_me_handler'; - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container): void { $handledFirewalls = []; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index dec107d65dc18..670d9263f45f4 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -65,7 +65,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->booleanNode('hide_user_not_found')->defaultTrue()->end() ->booleanNode('erase_credentials')->defaultTrue()->end() - ->booleanNode('enable_authenticator_manager')->defaultTrue()->end() + ->booleanNode('enable_authenticator_manager')->setDeprecated('symfony/security-bundle', '6.2', 'The "%node%" option at "%path%" is deprecated.')->defaultTrue()->end() ->arrayNode('access_decision_manager') ->addDefaultsIfNotSet() ->children() @@ -133,6 +133,7 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode) ->prototype('array') ->fixXmlConfig('ip') ->fixXmlConfig('method') + ->fixXmlConfig('attribute') ->children() ->scalarNode('request_matcher')->defaultNull()->end() ->scalarNode('requires_channel')->defaultNull()->end() @@ -147,6 +148,11 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode) ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() ->prototype('scalar')->end() ->end() + ->arrayNode('attributes') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->scalarNode('route')->defaultNull()->end() ->arrayNode('methods') ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() ->prototype('scalar')->end() @@ -210,10 +216,23 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->arrayNode('logout') ->treatTrueLike([]) ->canBeUnset() + ->beforeNormalization() + ->ifTrue(fn ($v): bool => \is_array($v) && (isset($v['csrf_token_generator']) xor isset($v['enable_csrf']))) + ->then(function (array $v): array { + if (isset($v['csrf_token_generator'])) { + $v['enable_csrf'] = true; + } elseif ($v['enable_csrf']) { + $v['csrf_token_generator'] = 'security.csrf.token_generator'; + } + + return $v; + }) + ->end() ->children() - ->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end() - ->scalarNode('csrf_token_generator')->cannotBeEmpty()->end() + ->booleanNode('enable_csrf')->defaultNull()->end() ->scalarNode('csrf_token_id')->defaultValue('logout')->end() + ->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end() + ->scalarNode('csrf_token_generator')->end() ->scalarNode('path')->defaultValue('/logout')->end() ->scalarNode('target')->defaultValue('/')->end() ->booleanNode('invalidate_session')->defaultTrue()->end() @@ -244,6 +263,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->scalarNode('provider')->end() ->scalarNode('parameter')->defaultValue('_switch_user')->end() ->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end() + ->scalarNode('target_route')->defaultValue(null)->end() ->end() ->end() ->arrayNode('required_badges') diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php new file mode 100644 index 0000000000000..1e38c58643638 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * AccessTokenFactory creates services for Access Token authentication. + * + * @author Florent Morselli + * + * @internal + */ +final class AccessTokenFactory extends AbstractFactory +{ + private const PRIORITY = -40; + + public function __construct() + { + $this->options = []; + $this->defaultFailureHandlerOptions = []; + $this->defaultSuccessHandlerOptions = []; + } + + public function addConfiguration(NodeDefinition $node): void + { + $builder = $node->children(); + + $builder + ->scalarNode('token_handler')->isRequired()->end() + ->scalarNode('user_provider')->defaultNull()->end() + ->scalarNode('realm')->defaultNull()->end() + ->scalarNode('success_handler')->defaultNull()->end() + ->scalarNode('failure_handler')->defaultNull()->end() + ->arrayNode('token_extractors') + ->fixXmlConfig('token_extractors') + ->beforeNormalization() + ->ifString() + ->then(static function (string $v): array { return [$v]; }) + ->end() + ->cannotBeEmpty() + ->defaultValue([ + 'security.access_token_extractor.header', + ]) + ->scalarPrototype()->end() + ->end() + ; + } + + public function getPriority(): int + { + return self::PRIORITY; + } + + public function getKey(): string + { + return 'access_token'; + } + + public function createAuthenticator(ContainerBuilder $container, string $firewallName, array $config, string $userProviderId): string + { + $userProvider = new Reference($config['user_provider'] ?? $userProviderId); + $successHandler = isset($config['success_handler']) ? new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config)) : null; + $failureHandler = isset($config['failure_handler']) ? new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config)) : null; + $authenticatorId = sprintf('security.authenticator.access_token.%s', $firewallName); + $extractorId = $this->createExtractor($container, $firewallName, $config['token_extractors']); + + $container + ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.access_token')) + ->replaceArgument(0, $userProvider) + ->replaceArgument(1, new Reference($config['token_handler'])) + ->replaceArgument(2, new Reference($extractorId)) + ->replaceArgument(3, $successHandler) + ->replaceArgument(4, $failureHandler) + ->replaceArgument(5, $config['realm']) + ; + + return $authenticatorId; + } + + /** + * @param array $extractors + */ + private function createExtractor(ContainerBuilder $container, string $firewallName, array $extractors): string + { + $aliases = [ + 'query_string' => 'security.access_token_extractor.query_string', + 'request_body' => 'security.access_token_extractor.request_body', + 'header' => 'security.access_token_extractor.header', + ]; + $extractors = array_map(static function (string $extractor) use ($aliases): string { + return $aliases[$extractor] ?? $extractor; + }, $extractors); + + if (1 === \count($extractors)) { + return current($extractors); + } + $extractorId = sprintf('security.authenticator.access_token.chain_extractor.%s', $firewallName); + $container + ->setDefinition($extractorId, new ChildDefinition('security.authenticator.access_token.chain_extractor')) + ->replaceArgument(0, array_map(function (string $extractorId): Reference {return new Reference($extractorId); }, $extractors)) + ; + + return $extractorId; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php index 9307cba86e3f3..303ae2a183196 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php @@ -39,9 +39,6 @@ public function getPriority(): int return self::PRIORITY; } - /** - * {@inheritdoc} - */ public function getKey(): string { return 'json-login'; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php index 5b2cfe781f490..6356702cc5d0b 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php @@ -11,14 +11,16 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface; +use Symfony\Component\Lock\LockInterface; use Symfony\Component\RateLimiter\RateLimiterFactory; +use Symfony\Component\RateLimiter\Storage\CacheStorage; use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter; /** @@ -60,20 +62,16 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal } if (!isset($config['limiter'])) { - if (!class_exists(FrameworkExtension::class) || !method_exists(FrameworkExtension::class, 'registerRateLimiter')) { - throw new \LogicException('You must either configure a rate limiter for "security.firewalls.'.$firewallName.'.login_throttling" or install symfony/framework-bundle:^5.2.'); - } - $limiterOptions = [ 'policy' => 'fixed_window', 'limit' => $config['max_attempts'], 'interval' => $config['interval'], 'lock_factory' => $config['lock_factory'], ]; - FrameworkExtension::registerRateLimiter($container, $localId = '_login_local_'.$firewallName, $limiterOptions); + $this->registerRateLimiter($container, $localId = '_login_local_'.$firewallName, $limiterOptions); $limiterOptions['limit'] = 5 * $config['max_attempts']; - FrameworkExtension::registerRateLimiter($container, $globalId = '_login_global_'.$firewallName, $limiterOptions); + $this->registerRateLimiter($container, $globalId = '_login_global_'.$firewallName, $limiterOptions); $container->register($config['limiter'] = 'security.login_throttling.'.$firewallName.'.limiter', DefaultLoginRateLimiter::class) ->addArgument(new Reference('limiter.'.$globalId)) @@ -88,4 +86,36 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal return []; } + + private function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig) + { + // default configuration (when used by other DI extensions) + $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; + + $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')); + + if (null !== $limiterConfig['lock_factory']) { + if (!interface_exists(LockInterface::class)) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name)); + } + if (!$container->hasDefinition('lock.factory.abstract')) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be configured.', $name)); + } + + $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory'])); + } + unset($limiterConfig['lock_factory']); + + if (null === $storageId = $limiterConfig['storage_service'] ?? null) { + $container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool'])); + } + + $limiter->replaceArgument(1, new Reference($storageId)); + unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']); + + $limiterConfig['id'] = $name; + $limiter->replaceArgument(0, $limiterConfig); + + $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter'); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index 2a85c2486c8f1..d3ec4633cf5ce 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -235,9 +235,6 @@ private function createTokenVerifier(ContainerBuilder $container, string $firewa return new Reference($tokenVerifierId, ContainerInterface::NULL_ON_INVALID_REFERENCE); } - /** - * {@inheritdoc} - */ public function prepend(ContainerBuilder $container) { $rememberMeSecureDefault = false; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 803fc5ed5d796..cd514bb44367d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -32,7 +33,14 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\Form\Extension\PasswordHasher\PasswordHasherExtension; +use Symfony\Component\HttpFoundation\ChainRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\AttributesRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\IpsRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\PortRequestMatcher; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; @@ -44,6 +52,7 @@ use Symfony\Component\Security\Core\Authorization\Strategy\PriorityStrategy; use Symfony\Component\Security\Core\Authorization\Strategy\UnanimousStrategy; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; +use Symfony\Component\Security\Core\User\ChainUserChecker; use Symfony\Component\Security\Core\User\ChainUserProvider; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -98,6 +107,7 @@ public function load(array $configs, ContainerBuilder $container) } $loader->load('security_authenticator.php'); + $loader->load('security_authenticator_access_token.php'); if ($container::willBeAvailable('symfony/twig-bridge', LogoutUrlExtension::class, ['symfony/security-bundle'])) { $loader->load('templating_twig.php'); @@ -112,6 +122,13 @@ public function load(array $configs, ContainerBuilder $container) if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'])) { $container->removeDefinition('security.expression_language'); $container->removeDefinition('security.access.expression_voter'); + $container->removeDefinition('security.is_granted_attribute_expression_language'); + } + + if (!class_exists(PasswordHasherExtension::class)) { + $container->removeDefinition('form.listener.password_hasher'); + $container->removeDefinition('form.type_extension.form.password_hasher'); + $container->removeDefinition('form.type_extension.password.password_hasher'); } // set some global scalars @@ -169,18 +186,13 @@ public function load(array $configs, ContainerBuilder $container) */ private function createStrategyDefinition(string $strategy, bool $allowIfAllAbstainDecisions, bool $allowIfEqualGrantedDeniedDecisions): Definition { - switch ($strategy) { - case MainConfiguration::STRATEGY_AFFIRMATIVE: - return new Definition(AffirmativeStrategy::class, [$allowIfAllAbstainDecisions]); - case MainConfiguration::STRATEGY_CONSENSUS: - return new Definition(ConsensusStrategy::class, [$allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions]); - case MainConfiguration::STRATEGY_UNANIMOUS: - return new Definition(UnanimousStrategy::class, [$allowIfAllAbstainDecisions]); - case MainConfiguration::STRATEGY_PRIORITY: - return new Definition(PriorityStrategy::class, [$allowIfAllAbstainDecisions]); - } - - throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.', $strategy)); + return match ($strategy) { + MainConfiguration::STRATEGY_AFFIRMATIVE => new Definition(AffirmativeStrategy::class, [$allowIfAllAbstainDecisions]), + MainConfiguration::STRATEGY_CONSENSUS => new Definition(ConsensusStrategy::class, [$allowIfAllAbstainDecisions, $allowIfEqualGrantedDeniedDecisions]), + MainConfiguration::STRATEGY_UNANIMOUS => new Definition(UnanimousStrategy::class, [$allowIfAllAbstainDecisions]), + MainConfiguration::STRATEGY_PRIORITY => new Definition(PriorityStrategy::class, [$allowIfAllAbstainDecisions]), + default => throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.', $strategy)), + }; } private function createRoleHierarchy(array $config, ContainerBuilder $container) @@ -199,24 +211,34 @@ private function createAuthorization(array $config, ContainerBuilder $container) { foreach ($config['access_control'] as $access) { if (isset($access['request_matcher'])) { - if ($access['path'] || $access['host'] || $access['port'] || $access['ips'] || $access['methods']) { + if ($access['path'] || $access['host'] || $access['port'] || $access['ips'] || $access['methods'] || $access['attributes'] || $access['route']) { throw new InvalidConfigurationException('The "request_matcher" option should not be specified alongside other options. Consider integrating your constraints inside your RequestMatcher directly.'); } $matcher = new Reference($access['request_matcher']); } else { + $attributes = $access['attributes']; + + if ($access['route']) { + if (\array_key_exists('_route', $attributes)) { + throw new InvalidConfigurationException('The "route" option should not be specified alongside "attributes._route" option. Use just one of the options.'); + } + $attributes['_route'] = $access['route']; + } + $matcher = $this->createRequestMatcher( $container, $access['path'], $access['host'], $access['port'], $access['methods'], - $access['ips'] + $access['ips'], + $attributes ); } - $attributes = $access['roles']; + $roles = $access['roles']; if ($access['allow_if']) { - $attributes[] = $this->createExpression($container, $access['allow_if']); + $roles[] = $this->createExpression($container, $access['allow_if']); } $emptyAccess = 0 === \count(array_filter($access)); @@ -226,7 +248,7 @@ private function createAuthorization(array $config, ContainerBuilder $container) } $container->getDefinition('security.access_map') - ->addMethodCall('add', [$matcher, $attributes, $access['requires_channel']]); + ->addMethodCall('add', [$matcher, $roles, $access['requires_channel']]); } // allow cache warm-up for expressions @@ -277,7 +299,7 @@ private function createFirewalls(array $config, ContainerBuilder $container) // load firewall map $mapDef = $container->getDefinition('security.firewall.map'); - $map = $authenticationProviders = $contextRefs = []; + $map = $authenticationProviders = $contextRefs = $authenticators = []; foreach ($firewalls as $name => $firewall) { if (isset($firewall['user_checker']) && 'security.user_checker' !== $firewall['user_checker']) { $customUserChecker = true; @@ -285,8 +307,17 @@ private function createFirewalls(array $config, ContainerBuilder $container) $configId = 'security.firewall.map.config.'.$name; - [$matcher, $listeners, $exceptionListener, $logoutListener] = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); + [$matcher, $listeners, $exceptionListener, $logoutListener, $firewallAuthenticators] = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); + if (!$firewallAuthenticators) { + $authenticators[$name] = null; + } else { + $firewallAuthenticatorRefs = []; + foreach ($firewallAuthenticators as $authenticatorId) { + $firewallAuthenticatorRefs[$authenticatorId] = new Reference($authenticatorId); + } + $authenticators[$name] = ServiceLocatorTagPass::register($container, $firewallAuthenticatorRefs); + } $contextId = 'security.firewall.map.context.'.$name; $isLazy = !$firewall['stateless'] && (!empty($firewall['anonymous']['lazy']) || $firewall['lazy']); $context = new ChildDefinition($isLazy ? 'security.firewall.lazy_context' : 'security.firewall.context'); @@ -301,6 +332,10 @@ private function createFirewalls(array $config, ContainerBuilder $container) $contextRefs[$contextId] = new Reference($contextId); $map[$contextId] = $matcher; } + $container + ->getDefinition('security.helper') + ->replaceArgument(1, $authenticators) + ; $container->setAlias('security.firewall.context_locator', (string) ServiceLocatorTagPass::register($container, $contextRefs)); @@ -335,7 +370,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ // Security disabled? if (false === $firewall['security']) { - return [$matcher, [], null, null]; + return [$matcher, [], null, null, []]; } $config->replaceArgument(4, $firewall['stateless']); @@ -363,6 +398,17 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ $container->register($firewallEventDispatcherId, EventDispatcher::class) ->addTag('event_dispatcher.dispatcher', ['name' => $firewallEventDispatcherId]); + $eventDispatcherLocator = $container->getDefinition('security.firewall.event_dispatcher_locator'); + $eventDispatcherLocator + ->replaceArgument(0, array_merge($eventDispatcherLocator->getArgument(0), [ + $id => new ServiceClosureArgument(new Reference($firewallEventDispatcherId)), + ])) + ; + + // Register Firewall-specific chained user checker + $container->register('security.user_checker.chain.'.$id, ChainUserChecker::class) + ->addArgument(new TaggedIteratorArgument('security.user_checker.'.$id)); + // Register listeners $listeners = []; $listenerKeys = []; @@ -406,7 +452,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); // add CSRF provider - if (isset($firewall['logout']['csrf_token_generator'])) { + if ($firewall['logout']['enable_csrf']) { $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_generator'])); } @@ -435,6 +481,8 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ false === $firewall['stateless'] && isset($firewall['context']) ? $firewall['context'] : null, ]) ; + + $config->replaceArgument(12, $firewall['logout']); } // Determine default entry point @@ -528,7 +576,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ $config->replaceArgument(10, $listenerKeys); $config->replaceArgument(11, $firewall['switch_user'] ?? null); - return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null]; + return [$matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null, $firewallAuthenticationProviders]; } private function createContextListener(ContainerBuilder $container, string $contextKey, ?string $firewallEventDispatcherId) @@ -821,6 +869,9 @@ private function createSwitchUserListener(ContainerBuilder $container, string $i if (!$userProvider) { throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "switch_user" listener on "%s" firewall is ambiguous as there is more than one registered provider.', $id)); } + if ($stateless && null !== $config['target_route']) { + throw new InvalidConfigurationException(sprintf('Cannot set a "target_route" for the "switch_user" listener on the "%s" firewall as it is stateless.', $id)); + } $switchUserListenerId = 'security.authentication.switchuser_listener.'.$id; $listener = $container->setDefinition($switchUserListenerId, new ChildDefinition('security.authentication.switchuser_listener')); @@ -830,6 +881,7 @@ private function createSwitchUserListener(ContainerBuilder $container, string $i $listener->replaceArgument(6, $config['parameter']); $listener->replaceArgument(7, $config['role']); $listener->replaceArgument(9, $stateless); + $listener->replaceArgument(11, $config['target_route']); return $switchUserListenerId; } @@ -859,7 +911,7 @@ private function createRequestMatcher(ContainerBuilder $container, string $path $methods = array_map('strtoupper', $methods); } - if (null !== $ips) { + if ($ips) { foreach ($ips as $ip) { $container->resolveEnvPlaceholders($ip, null, $usedEnvs); @@ -871,22 +923,58 @@ private function createRequestMatcher(ContainerBuilder $container, string $path } } - $id = '.security.request_matcher.'.ContainerBuilder::hash([$path, $host, $port, $methods, $ips, $attributes]); + $id = '.security.request_matcher.'.ContainerBuilder::hash([ChainRequestMatcher::class, $path, $host, $port, $methods, $ips, $attributes]); if (isset($this->requestMatchers[$id])) { return $this->requestMatchers[$id]; } - // only add arguments that are necessary - $arguments = [$path, $host, $methods, $ips, $attributes, null, $port]; - while (\count($arguments) > 0 && !end($arguments)) { - array_pop($arguments); + $arguments = []; + if ($methods) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([MethodRequestMatcher::class, $methods]))) { + $container->register($lid, MethodRequestMatcher::class)->setArguments([$methods]); + } + $arguments[] = new Reference($lid); + } + + if ($path) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([PathRequestMatcher::class, $path]))) { + $container->register($lid, PathRequestMatcher::class)->setArguments([$path]); + } + $arguments[] = new Reference($lid); + } + + if ($host) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([HostRequestMatcher::class, $host]))) { + $container->register($lid, HostRequestMatcher::class)->setArguments([$host]); + } + $arguments[] = new Reference($lid); + } + + if ($ips) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([IpsRequestMatcher::class, $ips]))) { + $container->register($lid, IpsRequestMatcher::class)->setArguments([$ips]); + } + $arguments[] = new Reference($lid); + } + + if ($attributes) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([AttributesRequestMatcher::class, $attributes]))) { + $container->register($lid, AttributesRequestMatcher::class)->setArguments([$attributes]); + } + $arguments[] = new Reference($lid); + } + + if ($port) { + if (!$container->hasDefinition($lid = '.security.request_matcher.'.ContainerBuilder::hash([PortRequestMatcher::class, $port]))) { + $container->register($lid, PortRequestMatcher::class)->setArguments([$port]); + } + $arguments[] = new Reference($lid); } $container - ->register($id, RequestMatcher::class) - ->setPublic(false) - ->setArguments($arguments) + ->register($id, ChainRequestMatcher::class) + ->setArguments([$arguments]) ; return $this->requestMatchers[$id] = new Reference($id); @@ -903,9 +991,6 @@ public function addUserProviderFactory(UserProviderFactoryInterface $factory) $this->userProviderFactories[] = $factory; } - /** - * {@inheritdoc} - */ public function getXsdValidationBasePath(): string|false { return __DIR__.'/../Resources/config/schema'; diff --git a/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php b/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php index 98f54c8634abd..9e91f3930ab08 100644 --- a/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php +++ b/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php @@ -56,9 +56,6 @@ public function onKernelFinishRequest(FinishRequestEvent $event) parent::onKernelFinishRequest($event); } - /** - * {@inheritdoc} - */ public static function getSubscribedEvents(): array { return [ diff --git a/src/Symfony/Bundle/SecurityBundle/LoginLink/FirewallAwareLoginLinkHandler.php b/src/Symfony/Bundle/SecurityBundle/LoginLink/FirewallAwareLoginLinkHandler.php index 5c61cfcfabad4..ff2ea2ddc9305 100644 --- a/src/Symfony/Bundle/SecurityBundle/LoginLink/FirewallAwareLoginLinkHandler.php +++ b/src/Symfony/Bundle/SecurityBundle/LoginLink/FirewallAwareLoginLinkHandler.php @@ -38,9 +38,9 @@ public function __construct(FirewallMap $firewallMap, ContainerInterface $loginL $this->requestStack = $requestStack; } - public function createLoginLink(UserInterface $user, Request $request = null): LoginLinkDetails + public function createLoginLink(UserInterface $user, Request $request = null, int $lifetime = null): LoginLinkDetails { - return $this->getForFirewall()->createLoginLink($user, $request); + return $this->getForFirewall()->createLoginLink($user, $request, $lifetime); } public function consumeLoginLink(Request $request): UserInterface diff --git a/src/Symfony/Bundle/SecurityBundle/RememberMe/DecoratedRememberMeHandler.php b/src/Symfony/Bundle/SecurityBundle/RememberMe/DecoratedRememberMeHandler.php index 56c2886c6c607..ed6d0ed20d5be 100644 --- a/src/Symfony/Bundle/SecurityBundle/RememberMe/DecoratedRememberMeHandler.php +++ b/src/Symfony/Bundle/SecurityBundle/RememberMe/DecoratedRememberMeHandler.php @@ -31,25 +31,16 @@ public function __construct(RememberMeHandlerInterface $handler) $this->handler = $handler; } - /** - * {@inheritDoc} - */ public function createRememberMeCookie(UserInterface $user): void { $this->handler->createRememberMeCookie($user); } - /** - * {@inheritDoc} - */ public function consumeRememberMeCookie(RememberMeDetails $rememberMeDetails): UserInterface { return $this->handler->consumeRememberMeCookie($rememberMeDetails); } - /** - * {@inheritDoc} - */ public function clearRememberMeCookie(): void { $this->handler->clearRememberMeCookie(); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/password_hasher.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/password_hasher.php index 50e1be8d981cd..2df037ad0d7ce 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/password_hasher.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/password_hasher.php @@ -11,6 +11,11 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\PasswordHasher\EventListener\PasswordHasherListener; +use Symfony\Component\Form\Extension\PasswordHasher\Type\FormTypePasswordHasherExtension; +use Symfony\Component\Form\Extension\PasswordHasher\Type\PasswordTypePasswordHasherExtension; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; @@ -26,5 +31,23 @@ ->args([service('security.password_hasher_factory')]) ->alias('security.password_hasher', 'security.user_password_hasher') ->alias(UserPasswordHasherInterface::class, 'security.password_hasher') + + ->set('form.listener.password_hasher', PasswordHasherListener::class) + ->args([ + service('security.password_hasher'), + service('property_accessor')->nullOnInvalid(), + ]) + + ->set('form.type_extension.form.password_hasher', FormTypePasswordHasherExtension::class) + ->args([ + service('form.listener.password_hasher'), + ]) + ->tag('form.type_extension', ['extended-type' => FormType::class]) + + ->set('form.type_extension.password.password_hasher', PasswordTypePasswordHasherExtension::class) + ->args([ + service('form.listener.password_hasher'), + ]) + ->tag('form.type_extension', ['extended-type' => PasswordType::class]) ; }; diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd b/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd index ccc41eda0c51b..33140fdae8d11 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd @@ -177,6 +177,7 @@ + @@ -377,6 +378,7 @@ + @@ -385,6 +387,7 @@ +
@@ -396,4 +399,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php index e655520b0e745..cae8893d28e5e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php @@ -13,10 +13,12 @@ use Symfony\Bundle\SecurityBundle\CacheWarmer\ExpressionCacheWarmer; use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; use Symfony\Bundle\SecurityBundle\Security\FirewallContext; use Symfony\Bundle\SecurityBundle\Security\FirewallMap; use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; use Symfony\Component\Ldap\Security\LdapUserProvider; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; @@ -33,7 +35,7 @@ use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; -use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Core\Security as LegacySecurity; use Symfony\Component\Security\Core\User\ChainUserProvider; use Symfony\Component\Security\Core\User\InMemoryUserChecker; use Symfony\Component\Security\Core\User\InMemoryUserProvider; @@ -41,6 +43,7 @@ use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use Symfony\Component\Security\Http\Controller\UserValueResolver; +use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener; use Symfony\Component\Security\Http\Firewall; use Symfony\Component\Security\Http\FirewallMapInterface; use Symfony\Component\Security\Http\HttpUtils; @@ -76,11 +79,22 @@ ->set('security.untracked_token_storage', TokenStorage::class) ->set('security.helper', Security::class) - ->args([service_locator([ - 'security.token_storage' => service('security.token_storage'), - 'security.authorization_checker' => service('security.authorization_checker'), - ])]) + ->args([ + service_locator([ + 'security.token_storage' => service('security.token_storage'), + 'security.authorization_checker' => service('security.authorization_checker'), + 'security.user_authenticator' => service('security.user_authenticator')->ignoreOnInvalid(), + 'request_stack' => service('request_stack'), + 'security.firewall.map' => service('security.firewall.map'), + 'security.user_checker' => service('security.user_checker'), + 'security.firewall.event_dispatcher_locator' => service('security.firewall.event_dispatcher_locator'), + 'security.csrf.token_manager' => service('security.csrf.token_manager')->ignoreOnInvalid(), + ]), + abstract_arg('authenticators'), + ]) ->alias(Security::class, 'security.helper') + ->alias(LegacySecurity::class, 'security.helper') + ->deprecate('symfony/security-bundle', '6.2', 'The "%alias_id%" service alias is deprecated, use "'.Security::class.'" instead.') ->set('security.user_value_resolver', UserValueResolver::class) ->args([ @@ -195,6 +209,7 @@ null, [], // listeners null, // switch_user + null, // logout ]) ->set('security.logout_url_generator', LogoutUrlGenerator::class) @@ -259,5 +274,19 @@ service('security.expression_language'), ]) ->tag('kernel.cache_warmer') + + ->set('controller.is_granted_attribute_listener', IsGrantedAttributeListener::class) + ->args([ + service('security.authorization_checker'), + service('security.is_granted_attribute_expression_language')->nullOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + + ->set('security.is_granted_attribute_expression_language', BaseExpressionLanguage::class) + ->args([service('cache.security_is_granted_attribute_expression_language')->nullOnInvalid()]) + + ->set('cache.security_is_granted_attribute_expression_language') + ->parent('cache.system') + ->tag('cache.pool') ; }; diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php new file mode 100644 index 0000000000000..17bc916c8d314 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Security\Http\AccessToken\ChainAccessTokenExtractor; +use Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor; +use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor; +use Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor; +use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('security.access_token_extractor.header', HeaderAccessTokenExtractor::class) + ->set('security.access_token_extractor.query_string', QueryAccessTokenExtractor::class) + ->set('security.access_token_extractor.request_body', FormEncodedBodyExtractor::class) + + ->set('security.authenticator.access_token', AccessTokenAuthenticator::class) + ->abstract() + ->args([ + abstract_arg('user provider'), + abstract_arg('access token handler'), + abstract_arg('access token extractor'), + null, + null, + null, + ]) + ->call('setTranslator', [service('translator')->ignoreOnInvalid()]) + + ->set('security.authenticator.access_token.chain_extractor', ChainAccessTokenExtractor::class) + ->abstract() + ->args([ + abstract_arg('access token extractors'), + ]) + ; +}; diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php index dfd94ad0d6f49..921aa90b8d730 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\Security\Http\AccessMap; use Symfony\Component\Security\Http\Authentication\CustomAuthenticationFailureHandler; use Symfony\Component\Security\Http\Authentication\CustomAuthenticationSuccessHandler; @@ -150,6 +151,8 @@ 'ROLE_ALLOWED_TO_SWITCH', service('event_dispatcher')->nullOnInvalid(), false, // Stateless + service('router')->nullOnInvalid(), + abstract_arg('Target Route'), ]) ->tag('monolog.logger', ['channel' => 'security']) @@ -160,5 +163,8 @@ service('security.access_map'), ]) ->tag('monolog.logger', ['channel' => 'security']) + + ->set('security.firewall.event_dispatcher_locator', ServiceLocator::class) + ->args([[]]) ; }; diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg index 1110c10a037d2..b11d1a4637476 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg +++ b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg @@ -1 +1,5 @@ - + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Security.php b/src/Symfony/Bundle/SecurityBundle/Security.php new file mode 100644 index 0000000000000..f2db5566b83c8 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Security.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Exception\LogicException; +use Symfony\Component\Security\Core\Exception\LogoutException; +use Symfony\Component\Security\Core\Security as LegacySecurity; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Event\LogoutEvent; +use Symfony\Component\Security\Http\ParameterBagUtils; +use Symfony\Contracts\Service\ServiceProviderInterface; + +/** + * Helper class for commonly-needed security tasks. + * + * @author Ryan Weaver + * @author Robin Chalas + * @author Arnaud Frézet + * + * @final + */ +class Security extends LegacySecurity +{ + public function __construct(private readonly ContainerInterface $container, private readonly array $authenticators = []) + { + parent::__construct($container, false); + } + + public function getFirewallConfig(Request $request): ?FirewallConfig + { + return $this->container->get('security.firewall.map')->getFirewallConfig($request); + } + + /** + * @param UserInterface $user The user to authenticate + * @param string|null $authenticatorName The authenticator name (e.g. "form_login") or service id (e.g. SomeApiKeyAuthenticator::class) - required only if multiple authenticators are configured + * @param string|null $firewallName The firewall name - required only if multiple firewalls are configured + */ + public function login(UserInterface $user, string $authenticatorName = null, string $firewallName = null): void + { + $request = $this->container->get('request_stack')->getCurrentRequest(); + $firewallName ??= $this->getFirewallConfig($request)?->getName(); + + if (!$firewallName) { + throw new LogicException('Unable to login as the current route is not covered by any firewall.'); + } + + $authenticator = $this->getAuthenticator($authenticatorName, $firewallName); + + $this->container->get('security.user_checker')->checkPreAuth($user); + $this->container->get('security.user_authenticator')->authenticateUser($user, $authenticator, $request); + } + + /** + * Logout the current user by dispatching the LogoutEvent. + * + * @param bool $validateCsrfToken Whether to look for a valid CSRF token based on the `logout` listener configuration + * + * @return Response|null The LogoutEvent's Response if any + * + * @throws LogoutException When $validateCsrfToken is true and the CSRF token is not found or invalid + */ + public function logout(bool $validateCsrfToken = true): ?Response + { + /** @var TokenStorageInterface $tokenStorage */ + $tokenStorage = $this->container->get('security.token_storage'); + + if (!($token = $tokenStorage->getToken()) || !$token->getUser()) { + throw new LogicException('Unable to logout as there is no logged-in user.'); + } + + $request = $this->container->get('request_stack')->getMainRequest(); + + if (!$firewallConfig = $this->container->get('security.firewall.map')->getFirewallConfig($request)) { + throw new LogicException('Unable to logout as the request is not behind a firewall.'); + } + + if ($validateCsrfToken) { + if (!$this->container->has('security.csrf.token_manager') || !$logoutConfig = $firewallConfig->getLogout()) { + throw new LogicException(sprintf('Unable to logout with CSRF token validation. Either make sure that CSRF protection is enabled and "logout" is configured on the "%s" firewall, or bypass CSRF token validation explicitly by passing false to the $validateCsrfToken argument of this method.', $firewallConfig->getName())); + } + $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $logoutConfig['csrf_parameter']); + if (!\is_string($csrfToken) || !$this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($logoutConfig['csrf_token_id'], $csrfToken))) { + throw new LogoutException('Invalid CSRF token.'); + } + } + + $logoutEvent = new LogoutEvent($request, $token); + $this->container->get('security.firewall.event_dispatcher_locator')->get($firewallConfig->getName())->dispatch($logoutEvent); + + $tokenStorage->setToken(null); + + return $logoutEvent->getResponse(); + } + + private function getAuthenticator(?string $authenticatorName, string $firewallName): AuthenticatorInterface + { + if (!\array_key_exists($firewallName, $this->authenticators)) { + throw new LogicException(sprintf('No authenticators found for firewall "%s".', $firewallName)); + } + + /** @var ServiceProviderInterface $firewallAuthenticatorLocator */ + $firewallAuthenticatorLocator = $this->authenticators[$firewallName]; + + if (!$authenticatorName) { + $authenticatorIds = array_keys($firewallAuthenticatorLocator->getProvidedServices()); + + if (!$authenticatorIds) { + throw new LogicException(sprintf('No authenticator was found for the firewall "%s".', $firewallName)); + } + if (1 < \count($authenticatorIds)) { + throw new LogicException(sprintf('Too many authenticators were found for the current firewall "%s". You must provide an instance of "%s" to login programmatically. The available authenticators for the firewall "%s" are "%s".', $firewallName, AuthenticatorInterface::class, $firewallName, implode('" ,"', $authenticatorIds))); + } + + return $firewallAuthenticatorLocator->get($authenticatorIds[0]); + } + + if ($firewallAuthenticatorLocator->has($authenticatorName)) { + return $firewallAuthenticatorLocator->get($authenticatorName); + } + + $authenticatorId = 'security.authenticator.'.$authenticatorName.'.'.$firewallName; + + if (!$firewallAuthenticatorLocator->has($authenticatorId)) { + throw new LogicException(sprintf('Unable to find an authenticator named "%s" for the firewall "%s". Available authenticators: "%s".', $authenticatorName, implode('", "', array_keys($firewallAuthenticatorLocator->getProvidedServices())))); + } + + return $firewallAuthenticatorLocator->get($authenticatorId); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php index ce9373f91db72..6525a23e4b9c5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php @@ -16,33 +16,21 @@ */ final class FirewallConfig { - private string $name; - private string $userChecker; - private ?string $requestMatcher; - private bool $securityEnabled; - private bool $stateless; - private ?string $provider; - private ?string $context; - private ?string $entryPoint; - private ?string $accessDeniedHandler; - private ?string $accessDeniedUrl; - private array $authenticators; - private ?array $switchUser; - - public function __construct(string $name, string $userChecker, string $requestMatcher = null, bool $securityEnabled = true, bool $stateless = false, string $provider = null, string $context = null, string $entryPoint = null, string $accessDeniedHandler = null, string $accessDeniedUrl = null, array $authenticators = [], array $switchUser = null) - { - $this->name = $name; - $this->userChecker = $userChecker; - $this->requestMatcher = $requestMatcher; - $this->securityEnabled = $securityEnabled; - $this->stateless = $stateless; - $this->provider = $provider; - $this->context = $context; - $this->entryPoint = $entryPoint; - $this->accessDeniedHandler = $accessDeniedHandler; - $this->accessDeniedUrl = $accessDeniedUrl; - $this->authenticators = $authenticators; - $this->switchUser = $switchUser; + public function __construct( + private readonly string $name, + private readonly string $userChecker, + private readonly ?string $requestMatcher = null, + private readonly bool $securityEnabled = true, + private readonly bool $stateless = false, + private readonly ?string $provider = null, + private readonly ?string $context = null, + private readonly ?string $entryPoint = null, + private readonly ?string $accessDeniedHandler = null, + private readonly ?string $accessDeniedUrl = null, + private readonly array $authenticators = [], + private readonly ?array $switchUser = null, + private readonly ?array $logout = null + ) { } public function getName(): string @@ -111,4 +99,9 @@ public function getSwitchUser(): ?array { return $this->switchUser; } + + public function getLogout(): ?array + { + return $this->logout; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php index 174f1d015e729..786457800367e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php @@ -38,9 +38,6 @@ public function __construct(FirewallMap $firewallMap, ContainerInterface $userAu $this->requestStack = $requestStack; } - /** - * {@inheritdoc} - */ public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []): ?Response { return $this->getForFirewall()->authenticateUser($user, $authenticator, $request, $badges); diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 01eeb74c3a094..3d90b1690a589 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -22,6 +22,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\ReplaceDecoratedRememberMeHandlerPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\SortFirewallListenersPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AccessTokenFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\CustomAuthenticatorFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory; @@ -69,6 +70,7 @@ public function build(ContainerBuilder $container) $extension->addAuthenticatorFactory(new CustomAuthenticatorFactory()); $extension->addAuthenticatorFactory(new LoginThrottlingFactory()); $extension->addAuthenticatorFactory(new LoginLinkFactory()); + $extension->addAuthenticatorFactory(new AccessTokenFactory()); $extension->addUserProviderFactory(new InMemoryFactory()); $extension->addUserProviderFactory(new LdapFactory()); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index c3283dae5cdd7..b5178dcde56a1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -239,14 +239,14 @@ public function providerCollectDecisionLog(): \Generator ], ]], [$decoratedVoter1, $decoratedVoter1], - [\get_class($voter1), \get_class($voter2)], + [$voter1::class, $voter2::class], [[ 'attributes' => ['view'], 'object' => new \stdClass(), 'result' => true, 'voter_details' => [ - ['class' => \get_class($voter1), 'attributes' => ['view'], 'vote' => VoterInterface::ACCESS_ABSTAIN], - ['class' => \get_class($voter2), 'attributes' => ['view'], 'vote' => VoterInterface::ACCESS_ABSTAIN], + ['class' => $voter1::class, 'attributes' => ['view'], 'vote' => VoterInterface::ACCESS_ABSTAIN], + ['class' => $voter2::class, 'attributes' => ['view'], 'vote' => VoterInterface::ACCESS_ABSTAIN], ], ]], ]; @@ -276,17 +276,17 @@ public function providerCollectDecisionLog(): \Generator ], ], [$decoratedVoter1, $decoratedVoter1], - [\get_class($voter1), \get_class($voter2)], + [$voter1::class, $voter2::class], [ [ 'attributes' => ['view', 'edit'], 'object' => new \stdClass(), 'result' => false, 'voter_details' => [ - ['class' => \get_class($voter1), 'attributes' => ['view'], 'vote' => VoterInterface::ACCESS_DENIED], - ['class' => \get_class($voter1), 'attributes' => ['edit'], 'vote' => VoterInterface::ACCESS_DENIED], - ['class' => \get_class($voter2), 'attributes' => ['view'], 'vote' => VoterInterface::ACCESS_GRANTED], - ['class' => \get_class($voter2), 'attributes' => ['edit'], 'vote' => VoterInterface::ACCESS_GRANTED], + ['class' => $voter1::class, 'attributes' => ['view'], 'vote' => VoterInterface::ACCESS_DENIED], + ['class' => $voter1::class, 'attributes' => ['edit'], 'vote' => VoterInterface::ACCESS_DENIED], + ['class' => $voter2::class, 'attributes' => ['view'], 'vote' => VoterInterface::ACCESS_GRANTED], + ['class' => $voter2::class, 'attributes' => ['edit'], 'vote' => VoterInterface::ACCESS_GRANTED], ], ], [ @@ -294,8 +294,8 @@ public function providerCollectDecisionLog(): \Generator 'object' => new \stdClass(), 'result' => true, 'voter_details' => [ - ['class' => \get_class($voter1), 'attributes' => ['update'], 'vote' => VoterInterface::ACCESS_GRANTED], - ['class' => \get_class($voter2), 'attributes' => ['update'], 'vote' => VoterInterface::ACCESS_GRANTED], + ['class' => $voter1::class, 'attributes' => ['update'], 'vote' => VoterInterface::ACCESS_GRANTED], + ['class' => $voter2::class, 'attributes' => ['update'], 'vote' => VoterInterface::ACCESS_GRANTED], ], ], ], diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php index bc4d9fe8876be..48052e9271a08 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php @@ -139,7 +139,6 @@ private function createContainer($sessionStorageOptions) $config = [ 'security' => [ - 'enable_authenticator_manager' => true, 'providers' => ['some_provider' => ['id' => 'foo']], 'firewalls' => ['some_firewall' => ['security' => false]], ], diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php index bd0ea68520abe..cecf1b04835ef 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/RegisterGlobalSecurityEventListenersPassTest.php @@ -56,7 +56,6 @@ protected function setUp(): void public function testEventIsPropagated(string $configuredEvent, string $registeredEvent) { $this->container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true]], ]); @@ -90,7 +89,6 @@ public function providePropagatedEvents(): array public function testRegisterCustomListener() { $this->container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true]], ]); @@ -111,7 +109,6 @@ public function testRegisterCustomListener() public function testRegisterCustomSubscriber() { $this->container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true]], ]); @@ -131,7 +128,6 @@ public function testRegisterCustomSubscriber() public function testMultipleFirewalls() { $this->container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true], 'api' => ['pattern' => '/api', 'http_basic' => true]], ]); @@ -161,7 +157,6 @@ public function testMultipleFirewalls() public function testListenerAlreadySpecific() { $this->container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => ['main' => ['pattern' => '/', 'http_basic' => true]], ]); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index 9fc846575eb5f..2909627f2ce35 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -19,6 +19,11 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpFoundation\RequestMatcher\AttributesRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\PortRequestMatcher; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; @@ -131,7 +136,7 @@ public function testFirewalls() [ 'simple', 'security.user_checker', - '.security.request_matcher.xmi9dcw', + '.security.request_matcher.h5ibf38', false, false, '', @@ -141,6 +146,7 @@ public function testFirewalls() '', [], null, + null, ], [ 'secure', @@ -164,12 +170,22 @@ public function testFirewalls() [ 'parameter' => '_switch_user', 'role' => 'ROLE_ALLOWED_TO_SWITCH', + 'target_route' => null, + ], + [ + 'csrf_parameter' => '_csrf_token', + 'csrf_token_id' => 'logout', + 'path' => '/logout', + 'target' => '/', + 'invalidate_session' => true, + 'delete_cookies' => [], + 'enable_csrf' => null, ], ], [ 'host', 'security.user_checker', - '.security.request_matcher.iw4hyjb', + '.security.request_matcher.bcmu4fb', true, false, 'security.user.provider.concrete.default', @@ -181,6 +197,7 @@ public function testFirewalls() 'http_basic', ], null, + null, ], [ 'with_user_checker', @@ -197,6 +214,7 @@ public function testFirewalls() 'http_basic', ], null, + null, ], ], $configs); @@ -235,20 +253,27 @@ public function testFirewallRequestMatchers() foreach ($arguments[1]->getValues() as $reference) { if ($reference instanceof Reference) { $definition = $container->getDefinition((string) $reference); - $matchers[] = $definition->getArguments(); + $matchers[] = $definition->getArgument(0); } } - $this->assertEquals([ - [ - '/login', - ], - [ - '/test', - 'foo\\.example\\.org', - ['GET', 'POST'], - ], - ], $matchers); + $this->assertCount(2, $matchers); + + $this->assertCount(1, $matchers[0]); + $def = $container->getDefinition((string) $matchers[0][0]); + $this->assertSame(PathRequestMatcher::class, $def->getClass()); + $this->assertSame('/login', $def->getArgument(0)); + + $this->assertCount(3, $matchers[1]); + $def = $container->getDefinition((string) $matchers[1][0]); + $this->assertSame(MethodRequestMatcher::class, $def->getClass()); + $this->assertSame(['GET', 'POST'], $def->getArgument(0)); + $def = $container->getDefinition((string) $matchers[1][1]); + $this->assertSame(PathRequestMatcher::class, $def->getClass()); + $this->assertSame('/test', $def->getArgument(0)); + $def = $container->getDefinition((string) $matchers[1][2]); + $this->assertSame(HostRequestMatcher::class, $def->getClass()); + $this->assertSame('foo\\.example\\.org', $def->getArgument(0)); } public function testUserCheckerAliasIsRegistered() @@ -281,23 +306,36 @@ public function testAccess() if (1 === $i) { $this->assertEquals(['ROLE_USER'], $attributes); $this->assertEquals('https', $channel); - $this->assertEquals( - ['/blog/524', null, ['GET', 'POST'], [], [], null, 8000], - $requestMatcher->getArguments() - ); + $this->assertCount(3, $requestMatcher->getArgument(0)); + $def = $container->getDefinition((string) $requestMatcher->getArgument(0)[0]); + $this->assertSame(MethodRequestMatcher::class, $def->getClass()); + $this->assertSame(['GET', 'POST'], $def->getArgument(0)); + $def = $container->getDefinition((string) $requestMatcher->getArgument(0)[1]); + $this->assertSame(PathRequestMatcher::class, $def->getClass()); + $this->assertSame('/blog/524', $def->getArgument(0)); + $def = $container->getDefinition((string) $requestMatcher->getArgument(0)[2]); + $this->assertSame(PortRequestMatcher::class, $def->getClass()); + $this->assertSame(8000, $def->getArgument(0)); } elseif (2 === $i) { $this->assertEquals(['IS_AUTHENTICATED_ANONYMOUSLY'], $attributes); $this->assertNull($channel); - $this->assertEquals( - ['/blog/.*'], - $requestMatcher->getArguments() - ); + $this->assertCount(1, $requestMatcher->getArgument(0)); + $def = $container->getDefinition((string) $requestMatcher->getArgument(0)[0]); + $this->assertSame(PathRequestMatcher::class, $def->getClass()); + $this->assertSame('/blog/.*', $def->getArgument(0)); } elseif (3 === $i) { $this->assertEquals('IS_AUTHENTICATED_ANONYMOUSLY', $attributes[0]); $expression = $container->getDefinition((string) $attributes[1])->getArgument(0); $this->assertEquals("token.getUserIdentifier() matches '/^admin/'", $expression); + } elseif (4 === $i) { + $this->assertEquals(['ROLE_ADMIN'], $attributes); + $def = $container->getDefinition((string) $requestMatcher->getArgument(0)[0]); + $this->assertSame(AttributesRequestMatcher::class, $def->getClass()); + $this->assertSame(['_controller' => 'AdminController::index', '_route' => 'admin'], $def->getArgument(0)); } } + + $this->assertCount(4, $matcherIds); } public function testMerge() diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_customized_config.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_customized_config.php index 6d011aebd5998..1d0a090f3f589 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_customized_config.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_customized_config.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'access_decision_manager' => [ 'allow_if_all_abstain' => true, 'allow_if_equal_granted_denied' => false, diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_default_strategy.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_default_strategy.php index cfa7751b7b4bd..1f0adbf3010f1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_default_strategy.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_default_strategy.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => [ 'memory' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_service.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_service.php index dee30bedc9c9f..8f615904ddf0d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_service.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_service.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'access_decision_manager' => [ 'service' => 'app.access_decision_manager', ], diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_service_and_strategy.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_service_and_strategy.php index d964561c42657..bd78bdf24d578 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_service_and_strategy.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_service_and_strategy.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'access_decision_manager' => [ 'service' => 'app.access_decision_manager', 'strategy' => 'affirmative', diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_strategy_service.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_strategy_service.php index 8024e3a72f25b..6a435c252fe86 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_strategy_service.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/access_decision_manager_strategy_service.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'access_decision_manager' => [ 'strategy_service' => 'app.custom_access_decision_strategy', ], diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_hasher.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_hasher.php index 6254f5747841f..341f772e87523 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_hasher.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/argon2i_hasher.php @@ -3,7 +3,6 @@ $this->load('container1.php'); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'password_hashers' => [ 'JMS\FooBundle\Entity\User7' => [ 'algorithm' => 'argon2i', diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/authenticator_manager.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/authenticator_manager.php index fa53fb980f67a..3dd4be36ed361 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/authenticator_manager.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/authenticator_manager.php @@ -3,7 +3,6 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => [ 'main' => [ 'required_badges' => [CsrfTokenBadge::class, 'RememberMeBadge'], diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/bcrypt_hasher.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/bcrypt_hasher.php index ac29b31e096e6..a416b3440d426 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/bcrypt_hasher.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/bcrypt_hasher.php @@ -3,7 +3,6 @@ $this->load('container1.php'); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'password_hashers' => [ 'JMS\FooBundle\Entity\User7' => [ 'algorithm' => 'bcrypt', diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php index f155c135a26ad..ea863ca306d75 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'password_hashers' => [ 'JMS\FooBundle\Entity\User1' => 'plaintext', 'JMS\FooBundle\Entity\User2' => [ @@ -97,6 +96,7 @@ ['path' => '/blog/524', 'role' => 'ROLE_USER', 'requires_channel' => 'https', 'methods' => ['get', 'POST'], 'port' => 8000], ['path' => '/blog/.*', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'], ['path' => '/blog/524', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'allow_if' => "token.getUserIdentifier() matches '/^admin/'"], + ['role' => 'ROLE_ADMIN', 'attributes' => ['_controller' => 'AdminController::index'], 'route' => 'admin'], ], 'role_hierarchy' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_provider.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_provider.php index eeec20726a588..68b8439a7de5a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_provider.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_provider.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => [ 'memory' => $memory = [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_undefined_provider.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_undefined_provider.php index dd90214810572..7c811cae1a4dd 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_undefined_provider.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_undefined_provider.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => [ 'memory' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_provider.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_provider.php index 8ddc21f13edc3..0a6a79f5f208c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_provider.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_provider.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => [ 'memory' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_undefined_provider.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_undefined_provider.php index 10661fae2010b..cc0b776e432c4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_undefined_provider.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_undefined_provider.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => [ 'memory' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/logout_delete_cookies.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/logout_delete_cookies.php index 7a40881b655d1..8ffe12e3eb929 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/logout_delete_cookies.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/logout_delete_cookies.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php index 03a5f1d28c87c..d0bd809579e89 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php @@ -3,7 +3,6 @@ $this->load('merge_import.php'); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php index 198935390cfd1..c85937d6ea2c9 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => [ 'main' => [ 'form_login' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_hasher.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_hasher.php index 3f68562c8a07c..342ea64805eff 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_hasher.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/migrating_hasher.php @@ -3,7 +3,6 @@ $this->load('container1.php'); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'password_hashers' => [ 'JMS\FooBundle\Entity\User7' => [ 'algorithm' => 'argon2i', diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/no_custom_user_checker.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/no_custom_user_checker.php index 29d93a1f2ec3e..8559ac7138825 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/no_custom_user_checker.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/no_custom_user_checker.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => [ 'memory' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php index 0e8963f9297e3..cfbef609a18db 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php @@ -1,7 +1,6 @@ loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/sodium_hasher.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/sodium_hasher.php index 8f17965b25bd6..3ec569ae9a6e2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/sodium_hasher.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/sodium_hasher.php @@ -3,7 +3,6 @@ $this->load('container1.php'); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'password_hashers' => [ 'JMS\FooBundle\Entity\User7' => [ 'algorithm' => 'sodium', diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_customized_config.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_customized_config.xml index 9116042908d12..012c8dac7b069 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_customized_config.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_customized_config.xml @@ -7,7 +7,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_default_strategy.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_default_strategy.xml index 85c8050cbc70f..1011f45c4accc 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_default_strategy.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_default_strategy.xml @@ -7,7 +7,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service.xml index 3e189b8c61ff6..ebc208c057168 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service.xml @@ -7,7 +7,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service_and_strategy.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service_and_strategy.xml index 5b70a4614addb..1f2133ffe02f1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service_and_strategy.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service_and_strategy.xml @@ -7,7 +7,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_strategy_service.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_strategy_service.xml index 94763b543f4ec..b161ddb5e671c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_strategy_service.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_strategy_service.xml @@ -7,7 +7,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/argon2i_hasher.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/argon2i_hasher.xml index 8168af333e13d..3dc2c685be321 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/argon2i_hasher.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/argon2i_hasher.xml @@ -12,7 +12,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/authenticator_manager.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/authenticator_manager.xml index 0185b81c440c8..54b5189a95dcb 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/authenticator_manager.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/authenticator_manager.xml @@ -7,7 +7,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge RememberMeBadge diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/bcrypt_hasher.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/bcrypt_hasher.xml index a1f784ed96761..d4c5d3ded1a11 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/bcrypt_hasher.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/bcrypt_hasher.xml @@ -12,7 +12,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml index c97dd5bf7ebf0..66dd30ea8d26a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml @@ -8,7 +8,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + @@ -76,5 +76,8 @@ + + AdminController::index + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_provider.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_provider.xml index 6f74984045970..52a64d2f42908 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_provider.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_provider.xml @@ -8,7 +8,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml index a80f613e00331..a61d597fad573 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml @@ -8,7 +8,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/listener_provider.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/listener_provider.xml index b45f378a5ba68..1ba3c5e5098e4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/listener_provider.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/listener_provider.xml @@ -8,7 +8,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/listener_undefined_provider.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/listener_undefined_provider.xml index bdf9d5ec837f0..314f25d263d71 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/listener_undefined_provider.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/listener_undefined_provider.xml @@ -8,7 +8,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/logout_delete_cookies.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/logout_delete_cookies.xml index e817b48901311..e66043c359a15 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/logout_delete_cookies.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/logout_delete_cookies.xml @@ -8,7 +8,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml index 569e20e65e3b9..8caaeeb153e2c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml @@ -12,7 +12,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml index c7c237f2fefa4..e518a7d9acd7a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml @@ -8,7 +8,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/migrating_hasher.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/migrating_hasher.xml index d0d0b4ff91ea7..a4a9d2010dd71 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/migrating_hasher.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/migrating_hasher.xml @@ -12,7 +12,7 @@ - + bcrypt diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/no_custom_user_checker.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/no_custom_user_checker.xml index c4dea529ba452..6b51f236a50a7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/no_custom_user_checker.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/no_custom_user_checker.xml @@ -7,7 +7,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml index 9921d6c5fe6b0..767397ada3515 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml @@ -8,7 +8,7 @@ http://symfony.com/schema/dic/security https://symfony.com/schema/dic/security/security-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/sodium_hasher.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/sodium_hasher.xml index 67d4d1304b31e..fd5cacef7b8a4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/sodium_hasher.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/sodium_hasher.xml @@ -12,7 +12,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_customized_config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_customized_config.yml index db0f2b551cf92..a8d044f1dec5d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_customized_config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_customized_config.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true access_decision_manager: allow_if_all_abstain: true allow_if_equal_granted_denied: false diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_default_strategy.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_default_strategy.yml index adfeffa5fb8c3..f7fb5adc2c5d4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_default_strategy.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_default_strategy.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true providers: default: memory: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_service.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_service.yml index b162a45916194..7ef3d8d93c3ab 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_service.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_service.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true access_decision_manager: service: app.access_decision_manager providers: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_service_and_strategy.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_service_and_strategy.yml index ced97bb5337b8..bd38b21ef3536 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_service_and_strategy.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_service_and_strategy.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true access_decision_manager: service: app.access_decision_manager strategy: affirmative diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_strategy_service.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_strategy_service.yml index 907cdfe8410c6..5d2afc61d78b6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_strategy_service.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/access_decision_manager_strategy_service.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true access_decision_manager: strategy_service: app.custom_access_decision_strategy providers: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_hasher.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_hasher.yml index 0ae8214f1246e..1079d6e5f8efc 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_hasher.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_hasher.yml @@ -2,7 +2,6 @@ imports: - { resource: container1.yml } security: - enable_authenticator_manager: true password_hashers: JMS\FooBundle\Entity\User7: algorithm: argon2i diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/authenticator_manager.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/authenticator_manager.yml index 7efae5356f0f4..34113fb910b18 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/authenticator_manager.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/authenticator_manager.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true firewalls: main: required_badges: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/bcrypt_hasher.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/bcrypt_hasher.yml index c8a4a71ce4667..8e8397486d68e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/bcrypt_hasher.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/bcrypt_hasher.yml @@ -2,7 +2,6 @@ imports: - { resource: container1.yml } security: - enable_authenticator_manager: true password_hashers: JMS\FooBundle\Entity\User7: algorithm: bcrypt diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml index 16de382cc1f2f..a3123867d8fea 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true password_hashers: JMS\FooBundle\Entity\User1: plaintext JMS\FooBundle\Entity\User2: @@ -84,3 +83,4 @@ security: path: /blog/.* role: IS_AUTHENTICATED_ANONYMOUSLY - { path: /blog/524, role: IS_AUTHENTICATED_ANONYMOUSLY, allow_if: "token.getUserIdentifier() matches '/^admin/'" } + - { role: ROLE_ADMIN, attributes: { _controller: 'AdminController::index' }, route: 'admin' } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/firewall_provider.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/firewall_provider.yml index 9aa008a75d302..11c329aa8e2fe 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/firewall_provider.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/firewall_provider.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true providers: default: memory: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/firewall_undefined_provider.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/firewall_undefined_provider.yml index e10a2eaf398b1..ec2664054009c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/firewall_undefined_provider.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/firewall_undefined_provider.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true providers: default: memory: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/listener_provider.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/listener_provider.yml index c3c1c282898a0..652f23b5f0425 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/listener_provider.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/listener_provider.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true providers: default: memory: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/listener_undefined_provider.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/listener_undefined_provider.yml index 3cab5355ddd4e..1916df4c2e7ca 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/listener_undefined_provider.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/listener_undefined_provider.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true providers: default: memory: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/logout_delete_cookies.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/logout_delete_cookies.yml index a94bc1ff8f32b..09bea8c13ab37 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/logout_delete_cookies.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/logout_delete_cookies.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true providers: default: id: foo diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml index 50ae533138613..60c0bbea558e7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml @@ -2,7 +2,6 @@ imports: - { resource: merge_import.yml } security: - enable_authenticator_manager: true providers: default: { id: foo } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml index bf91f016a29c4..4f8db0a09f7b4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true firewalls: main: form_login: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/migrating_hasher.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/migrating_hasher.yml index 60ac97f48fce9..8657b1ee744ad 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/migrating_hasher.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/migrating_hasher.yml @@ -2,7 +2,6 @@ imports: - { resource: container1.yml } security: - enable_authenticator_manager: true password_hashers: JMS\FooBundle\Entity\User7: algorithm: argon2i diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/no_custom_user_checker.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/no_custom_user_checker.yml index d42c45edb0a31..8b7b2e9296cbb 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/no_custom_user_checker.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/no_custom_user_checker.yml @@ -1,6 +1,4 @@ security: - enable_authenticator_manager: true - providers: default: memory: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml index b4a1a8f6e49b5..a521c8c6a803d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml @@ -1,6 +1,4 @@ security: - enable_authenticator_manager: true - providers: default: id: foo diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/sodium_hasher.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/sodium_hasher.yml index 7c417bfe71d08..955a0b2a2059c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/sodium_hasher.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/sodium_hasher.yml @@ -2,7 +2,6 @@ imports: - { resource: container1.yml } security: - enable_authenticator_manager: true password_hashers: JMS\FooBundle\Entity\User7: algorithm: sodium diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php index cec8b019b2000..c64a7b49ba56d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php @@ -88,6 +88,55 @@ public function testCsrfAliases() $this->assertEquals('a_token_id', $processedConfig['firewalls']['stub']['logout']['csrf_token_id']); } + public function testLogoutCsrf() + { + $config = [ + 'firewalls' => [ + 'custom_token_generator' => [ + 'logout' => [ + 'csrf_token_generator' => 'a_token_generator', + 'csrf_token_id' => 'a_token_id', + ], + ], + 'default_token_generator' => [ + 'logout' => [ + 'enable_csrf' => true, + 'csrf_token_id' => 'a_token_id', + ], + ], + 'disabled_csrf' => [ + 'logout' => [ + 'enable_csrf' => false, + ], + ], + 'empty' => [ + 'logout' => true, + ], + ], + ]; + $config = array_merge(static::$minimalConfig, $config); + + $processor = new Processor(); + $configuration = new MainConfiguration([], []); + $processedConfig = $processor->processConfiguration($configuration, [$config]); + + $assertions = [ + 'custom_token_generator' => [true, 'a_token_generator'], + 'default_token_generator' => [true, 'security.csrf.token_generator'], + 'disabled_csrf' => [false, null], + 'empty' => [false, null], + ]; + foreach ($assertions as $firewallName => [$enabled, $tokenGenerator]) { + $this->assertEquals($enabled, $processedConfig['firewalls'][$firewallName]['logout']['enable_csrf']); + if ($tokenGenerator) { + $this->assertEquals($tokenGenerator, $processedConfig['firewalls'][$firewallName]['logout']['csrf_token_generator']); + $this->assertEquals('a_token_id', $processedConfig['firewalls'][$firewallName]['logout']['csrf_token_id']); + } else { + $this->assertArrayNotHasKey('csrf_token_generator', $processedConfig['firewalls'][$firewallName]['logout']); + } + } + } + public function testDefaultUserCheckers() { $processor = new Processor(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php new file mode 100644 index 0000000000000..f31798113b75c --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AccessTokenFactory; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class AccessTokenFactoryTest extends TestCase +{ + public function testBasicServiceConfiguration() + { + $container = new ContainerBuilder(); + $config = [ + 'token_handler' => 'in_memory_token_handler_service_id', + 'success_handler' => 'success_handler_service_id', + 'failure_handler' => 'failure_handler_service_id', + 'token_extractors' => ['BAR', 'FOO'], + ]; + + $factory = new AccessTokenFactory(); + $finalizedConfig = $this->processConfig($config, $factory); + + $factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider'); + + $this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1')); + } + + public function testDefaultServiceConfiguration() + { + $container = new ContainerBuilder(); + $config = [ + 'token_handler' => 'in_memory_token_handler_service_id', + ]; + + $factory = new AccessTokenFactory(); + $finalizedConfig = $this->processConfig($config, $factory); + + $factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider'); + + $this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1')); + } + + public function testNoExtractorsDefined() + { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('The path "access_token.token_extractors" should have at least 1 element(s) defined.'); + $config = [ + 'token_handler' => 'in_memory_token_handler_service_id', + 'success_handler' => 'success_handler_service_id', + 'failure_handler' => 'failure_handler_service_id', + 'token_extractors' => [], + ]; + + $factory = new AccessTokenFactory(); + $this->processConfig($config, $factory); + } + + public function testNoHandlerDefined() + { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('The child config "token_handler" under "access_token" must be configured.'); + $config = [ + 'success_handler' => 'success_handler_service_id', + 'failure_handler' => 'failure_handler_service_id', + ]; + + $factory = new AccessTokenFactory(); + $this->processConfig($config, $factory); + } + + private function processConfig(array $config, AccessTokenFactory $factory) + { + $nodeDefinition = new ArrayNodeDefinition('access_token'); + $factory->addConfiguration($nodeDefinition); + + $node = $nodeDefinition->getNode(); + $normalizedConfig = $node->normalize($config); + + return $node->finalize($normalizedConfig); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 5323c16f3b2a1..bcfb169ea321d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -26,7 +26,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; @@ -50,7 +50,6 @@ public function testInvalidCheckPath() $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -78,7 +77,6 @@ public function testFirewallWithInvalidUserProvider() $extension->addUserProviderFactory(new DummyProvider()); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'my_foo' => ['foo' => []], ], @@ -99,7 +97,6 @@ public function testDisableRoleHierarchyVoter() $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -124,7 +121,6 @@ public function testSwitchUserNotStatelessOnStatelessFirewall() $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -147,7 +143,6 @@ public function testPerListenerProvider() { $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'first' => ['id' => 'foo'], 'second' => ['id' => 'bar'], @@ -170,7 +165,6 @@ public function testMissingProviderForListener() $this->expectExceptionMessage('Not configuring explicitly the provider for the "http_basic" authenticator on "ambiguous" firewall is ambiguous as there is more than one registered provider.'); $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'first' => ['id' => 'foo'], 'second' => ['id' => 'bar'], @@ -191,7 +185,6 @@ public function testPerListenerProviderWithRememberMeAndAnonymous() { $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'first' => ['id' => 'foo'], 'second' => ['id' => 'bar'], @@ -216,7 +209,6 @@ public function testRegisterRequestMatchersWithAllowIfExpression() $rawExpression = "'foo' == 'bar' or 1 in [1, 3, 3]"; $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -256,11 +248,10 @@ public function testRegisterAccessControlWithSpecifiedRequestMatcherService() $container = $this->getRawContainer(); $requestMatcherId = 'My\Test\RequestMatcher'; - $requestMatcher = new RequestMatcher('/'); + $requestMatcher = new PathRequestMatcher('/'); $container->set($requestMatcherId, $requestMatcher); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -291,11 +282,10 @@ public function testRegisterAccessControlWithRequestMatcherAndAdditionalOptionsT $container = $this->getRawContainer(); $requestMatcherId = 'My\Test\RequestMatcher'; - $requestMatcher = new RequestMatcher('/'); + $requestMatcher = new PathRequestMatcher('/'); $container->set($requestMatcherId, $requestMatcher); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -323,13 +313,108 @@ public function provideAdditionalRequestMatcherConstraints() yield 'Invalid configuration with port' => [['port' => 80]]; yield 'Invalid configuration with methods' => [['methods' => ['POST']]]; yield 'Invalid configuration with ips' => [['ips' => ['0.0.0.0']]]; + yield 'Invalid configuration with attributes' => [['attributes' => ['_route' => 'foo_route']]]; + yield 'Invalid configuration with route' => [['route' => 'foo_route']]; + } + + public function testRegisterAccessControlWithSpecifiedAttributes() + { + $container = $this->getRawContainer(); + $container->loadFromExtension('security', [ + 'providers' => [ + 'default' => ['id' => 'foo'], + ], + 'firewalls' => [ + 'some_firewall' => [ + 'pattern' => '/.*', + 'http_basic' => [], + ], + ], + 'access_control' => [ + ['attributes' => ['_route' => 'foo_route']], + ], + ]); + + $container->compile(); + + $accessMap = $container->getDefinition('security.access_map'); + $this->assertCount(1, $accessMap->getMethodCalls()); + $call = $accessMap->getMethodCalls()[0]; + $this->assertSame('add', $call[0]); + $args = $call[1]; + + $chainRequestMatcherDefinition = $container->getDefinition((string) $args[0]); + $chainRequestMatcherConstructorArguments = $chainRequestMatcherDefinition->getArguments(); + $attributesRequestMatcher = $container->getDefinition((string) $chainRequestMatcherConstructorArguments[0][0]); + + $this->assertCount(1, $attributesRequestMatcher->getArguments()); + $this->assertArrayHasKey('_route', $attributesRequestMatcher->getArgument(0)); + $this->assertSame('foo_route', $attributesRequestMatcher->getArgument(0)['_route']); + } + + public function testRegisterAccessControlWithSpecifiedRoute() + { + $container = $this->getRawContainer(); + $container->loadFromExtension('security', [ + 'providers' => [ + 'default' => ['id' => 'foo'], + ], + 'firewalls' => [ + 'some_firewall' => [ + 'pattern' => '/.*', + 'http_basic' => [], + ], + ], + 'access_control' => [ + ['route' => 'foo_route'], + ], + ]); + + $container->compile(); + + $accessMap = $container->getDefinition('security.access_map'); + $this->assertCount(1, $accessMap->getMethodCalls()); + $call = $accessMap->getMethodCalls()[0]; + $this->assertSame('add', $call[0]); + $args = $call[1]; + + $chainRequestMatcherDefinition = $container->getDefinition((string) $args[0]); + $chainRequestMatcherConstructorArguments = $chainRequestMatcherDefinition->getArguments(); + $attributesRequestMatcher = $container->getDefinition((string) $chainRequestMatcherConstructorArguments[0][0]); + + $this->assertCount(1, $attributesRequestMatcher->getArguments()); + $this->assertArrayHasKey('_route', $attributesRequestMatcher->getArgument(0)); + $this->assertSame('foo_route', $attributesRequestMatcher->getArgument(0)['_route']); + } + + public function testRegisterAccessControlWithSpecifiedAttributesThrowsException() + { + $container = $this->getRawContainer(); + $container->loadFromExtension('security', [ + 'providers' => [ + 'default' => ['id' => 'foo'], + ], + 'firewalls' => [ + 'some_firewall' => [ + 'pattern' => '/.*', + 'http_basic' => [], + ], + ], + 'access_control' => [ + ['route' => 'anything', 'attributes' => ['_route' => 'foo_route']], + ], + ]); + + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('The "route" option should not be specified alongside "attributes._route" option. Use just one of the options.'); + + $container->compile(); } public function testRemovesExpressionCacheWarmerDefinitionIfNoExpressions() { $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -350,7 +435,6 @@ public function testRegisterTheUserProviderAlias() $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -373,7 +457,6 @@ public function testDoNotRegisterTheUserProviderAliasWithMultipleProviders() $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'first' => ['id' => 'foo'], 'second' => ['id' => 'bar'], @@ -400,8 +483,6 @@ public function testFirewallWithNoUserProviderTriggerDeprecation() $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, - 'providers' => [ 'first' => ['id' => 'foo'], 'second' => ['id' => 'foo'], @@ -427,7 +508,6 @@ public function testAcceptableAccessControlIps($ips) $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -453,7 +533,6 @@ public function testCustomRememberMeHandler() $container->register('custom_remember_me', \stdClass::class); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => [ 'default' => [ 'remember_me' => ['secret' => 'very', 'service' => 'custom_remember_me'], @@ -474,7 +553,6 @@ public function testSecretRememberMeHasher() $container->register('custom_remember_me', \stdClass::class); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => [ 'default' => [ 'remember_me' => ['secret' => 'very'], @@ -494,7 +572,6 @@ public function testSecretRememberMeHandler() $container->register('custom_remember_me', \stdClass::class); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => [ 'default' => [ 'remember_me' => ['secret' => 'very', 'token_provider' => 'token_provider_id'], @@ -543,7 +620,6 @@ public function testSwitchUserWithSeveralDefinedProvidersButNoFirewallRootProvid { $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'first' => ['id' => 'foo'], 'second' => ['id' => 'bar'], @@ -568,7 +644,6 @@ public function testInvalidAccessControlWithEmptyRow() $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -594,7 +669,6 @@ public function testValidAccessControlWithEmptyRow() $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'default' => ['id' => 'foo'], ], @@ -625,7 +699,6 @@ public function testEntryPointRequired(array $firewall, $messageRegex) $container = $this->getRawContainer(); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'first' => ['id' => 'users'], ], @@ -655,7 +728,6 @@ public function testConfigureCustomAuthenticator(array $firewall, array $expecte $container = $this->getRawContainer(); $container->register(TestAuthenticator::class); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'providers' => [ 'first' => ['id' => 'users'], ], @@ -689,7 +761,6 @@ public function testCompilesWithoutSessionListenerWithStatelessFirewallWithAuthe $firewallId = 'stateless_firewall'; $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => [ $firewallId => [ 'pattern' => '/.*', @@ -710,7 +781,6 @@ public function testCompilesWithSessionListenerWithStatefulllFirewallWithAuthent $firewallId = 'statefull_firewall'; $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => [ $firewallId => [ 'pattern' => '/.*', @@ -734,7 +804,6 @@ public function testUserCheckerWithAuthenticatorManager(array $config, string $e $container->register(TestUserChecker::class); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => [ 'main' => array_merge([ 'pattern' => '/.*', @@ -764,7 +833,6 @@ public function testConfigureCustomFirewallListener() $extension->addAuthenticatorFactory(new TestFirewallListenerFactory()); $container->loadFromExtension('security', [ - 'enable_authenticator_manager' => true, 'firewalls' => [ 'main' => [ 'custom_listener' => true, diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php new file mode 100644 index 0000000000000..01b205737dd59 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AccessTokenTest.php @@ -0,0 +1,318 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\HttpFoundation\Response; + +class AccessTokenTest extends AbstractWebTestCase +{ + public function testNoTokenHandlerConfiguredShouldFail() + { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('The child config "token_handler" under "security.firewalls.main.access_token" must be configured.'); + $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_no_handler.yml']); + } + + public function testNoTokenExtractorsConfiguredShouldFail() + { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('The path "security.firewalls.main.access_token.token_extractors" should have at least 1 element(s) defined.'); + $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_no_extractors.yml']); + } + + public function testAnonymousAccessIsGranted() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_anonymous.yml']); + $client->request('GET', '/bar'); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome anonymous!'], json_decode($response->getContent(), true)); + } + + public function testDefaultFormEncodedBodySuccess() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_body_default.yml']); + $client->request('POST', '/foo', ['access_token' => 'VALID_ACCESS_TOKEN'], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true)); + } + + /** + * @dataProvider defaultFormEncodedBodyFailureData + */ + public function testDefaultFormEncodedBodyFailure(array $parameters, array $headers) + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_body_default.yml']); + $client->request('POST', '/foo', $parameters, [], $headers); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(401, $response->getStatusCode()); + $this->assertSame('', $response->getContent()); + $this->assertSame('Bearer realm="My API",error="invalid_token",error_description="Invalid credentials."', $response->headers->get('WWW-Authenticate')); + } + + public function testDefaultMissingFormEncodedBodyFail() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_body_default.yml']); + $client->request('GET', '/foo'); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(401, $response->getStatusCode()); + } + + public function testCustomFormEncodedBodySuccess() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_body_custom.yml']); + $client->request('POST', '/foo', ['secured_token' => 'VALID_ACCESS_TOKEN'], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Good game @dunglas!'], json_decode($response->getContent(), true)); + } + + /** + * @dataProvider customFormEncodedBodyFailure + */ + public function testCustomFormEncodedBodyFailure(array $parameters, array $headers) + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_body_custom.yml']); + $client->request('POST', '/foo', $parameters, [], $headers); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(500, $response->getStatusCode()); + $this->assertSame(['message' => 'Something went wrong'], json_decode($response->getContent(), true)); + $this->assertFalse($response->headers->has('WWW-Authenticate')); + } + + public function testCustomMissingFormEncodedBodyShouldFail() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_body_custom.yml']); + $client->request('POST', '/foo'); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(401, $response->getStatusCode()); + } + + public function defaultFormEncodedBodyFailureData(): iterable + { + yield [['access_token' => 'INVALID_ACCESS_TOKEN'], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']]; + } + + public function customFormEncodedBodyFailure(): iterable + { + yield [['secured_token' => 'INVALID_ACCESS_TOKEN'], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']]; + } + + public function testDefaultHeaderAccessTokenSuccess() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_header_default.yml']); + $client->request('GET', '/foo', [], [], ['HTTP_AUTHORIZATION' => 'Bearer VALID_ACCESS_TOKEN']); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true)); + } + + public function testMultipleAccessTokenExtractorSuccess() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_multiple_extractors.yml']); + $client->request('GET', '/foo', [], [], ['HTTP_AUTHORIZATION' => 'Bearer VALID_ACCESS_TOKEN']); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true)); + } + + /** + * @dataProvider defaultHeaderAccessTokenFailureData + */ + public function testDefaultHeaderAccessTokenFailure(array $headers) + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_header_default.yml']); + $client->request('GET', '/foo', [], [], $headers); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(401, $response->getStatusCode()); + $this->assertSame('', $response->getContent()); + $this->assertSame('Bearer realm="My API",error="invalid_token",error_description="Invalid credentials."', $response->headers->get('WWW-Authenticate')); + } + + /** + * @dataProvider defaultMissingHeaderAccessTokenFailData + */ + public function testDefaultMissingHeaderAccessTokenFail(array $headers) + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_header_default.yml']); + $client->request('GET', '/foo', [], [], $headers); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(401, $response->getStatusCode()); + } + + public function testCustomHeaderAccessTokenSuccess() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_header_custom.yml']); + $client->request('GET', '/foo', [], [], ['HTTP_X_AUTH_TOKEN' => 'VALID_ACCESS_TOKEN']); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Good game @dunglas!'], json_decode($response->getContent(), true)); + } + + /** + * @dataProvider customHeaderAccessTokenFailure + */ + public function testCustomHeaderAccessTokenFailure(array $headers, int $errorCode) + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_header_custom.yml']); + $client->request('GET', '/foo', [], [], $headers); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame($errorCode, $response->getStatusCode()); + $this->assertFalse($response->headers->has('WWW-Authenticate')); + } + + /** + * @dataProvider customMissingHeaderAccessTokenShouldFail + */ + public function testCustomMissingHeaderAccessTokenShouldFail(array $headers) + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_header_custom.yml']); + $client->request('GET', '/foo', [], [], $headers); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(401, $response->getStatusCode()); + } + + public function defaultHeaderAccessTokenFailureData(): iterable + { + yield [['HTTP_AUTHORIZATION' => 'Bearer INVALID_ACCESS_TOKEN']]; + } + + public function defaultMissingHeaderAccessTokenFailData(): iterable + { + yield [['HTTP_AUTHORIZATION' => 'JWT INVALID_TOKEN_TYPE']]; + yield [['HTTP_X_FOO' => 'Missing-Header']]; + yield [['HTTP_X_AUTH_TOKEN' => 'this is not a token']]; + } + + public function customHeaderAccessTokenFailure(): iterable + { + yield [['HTTP_X_AUTH_TOKEN' => 'INVALID_ACCESS_TOKEN'], 500]; + } + + public function customMissingHeaderAccessTokenShouldFail(): iterable + { + yield [[]]; + yield [['HTTP_AUTHORIZATION' => 'Bearer this is not a token']]; + } + + public function testDefaultQueryAccessTokenSuccess() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_query_default.yml']); + $client->request('GET', '/foo?access_token=VALID_ACCESS_TOKEN'); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome @dunglas!'], json_decode($response->getContent(), true)); + } + + /** + * @dataProvider defaultQueryAccessTokenFailureData + */ + public function testDefaultQueryAccessTokenFailure(string $query) + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_query_default.yml']); + $client->request('GET', $query); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(401, $response->getStatusCode()); + $this->assertSame('', $response->getContent()); + $this->assertSame('Bearer realm="My API",error="invalid_token",error_description="Invalid credentials."', $response->headers->get('WWW-Authenticate')); + } + + public function testDefaultMissingQueryAccessTokenFail() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_query_default.yml']); + $client->request('GET', '/foo'); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(401, $response->getStatusCode()); + } + + public function testCustomQueryAccessTokenSuccess() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_query_custom.yml']); + $client->request('GET', '/foo?protection_token=VALID_ACCESS_TOKEN'); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Good game @dunglas!'], json_decode($response->getContent(), true)); + } + + /** + * @dataProvider customQueryAccessTokenFailure + */ + public function testCustomQueryAccessTokenFailure(string $query) + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_query_custom.yml']); + $client->request('GET', $query); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(500, $response->getStatusCode()); + $this->assertSame(['message' => 'Something went wrong'], json_decode($response->getContent(), true)); + $this->assertFalse($response->headers->has('WWW-Authenticate')); + } + + public function testCustomMissingQueryAccessTokenShouldFail() + { + $client = $this->createClient(['test_case' => 'AccessToken', 'root_config' => 'config_query_custom.yml']); + $client->request('GET', '/foo'); + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(401, $response->getStatusCode()); + } + + public function defaultQueryAccessTokenFailureData(): iterable + { + yield ['/foo?access_token=INVALID_ACCESS_TOKEN']; + } + + public function customQueryAccessTokenFailure(): iterable + { + yield ['/foo?protection_token=INVALID_ACCESS_TOKEN']; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/AccessTokenBundle.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/AccessTokenBundle.php new file mode 100644 index 0000000000000..a59c7cd47feef --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/AccessTokenBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class AccessTokenBundle extends Bundle +{ +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Controller/BarController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Controller/BarController.php new file mode 100644 index 0000000000000..f1cc399926e65 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Controller/BarController.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Controller; + +use Symfony\Component\HttpFoundation\JsonResponse; + +class BarController +{ + public function __invoke(): JsonResponse + { + return new JsonResponse(['message' => 'Welcome anonymous!']); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Controller/FooController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Controller/FooController.php new file mode 100644 index 0000000000000..7bc8e73502b78 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Controller/FooController.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Controller; + +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\Security\Core\User\UserInterface; + +class FooController +{ + public function __invoke(UserInterface $user): JsonResponse + { + return new JsonResponse(['message' => sprintf('Welcome @%s!', $user->getUserIdentifier())]); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php new file mode 100644 index 0000000000000..0d1e9e0c0e7fc --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler; + +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; + +class AccessTokenHandler implements AccessTokenHandlerInterface +{ + public function __construct() + { + } + + public function getUserIdentifierFrom(string $accessToken): string + { + return match ($accessToken) { + 'VALID_ACCESS_TOKEN' => 'dunglas', + default => throw new BadCredentialsException('Invalid credentials.'), + }; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Http/JsonAuthenticationFailureHandler.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Http/JsonAuthenticationFailureHandler.php new file mode 100644 index 0000000000000..ea6e608238721 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Http/JsonAuthenticationFailureHandler.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Http; + +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; + +class JsonAuthenticationFailureHandler implements AuthenticationFailureHandlerInterface +{ + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + return new JsonResponse(['message' => 'Something went wrong'], 500); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Http/JsonAuthenticationSuccessHandler.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Http/JsonAuthenticationSuccessHandler.php new file mode 100644 index 0000000000000..fd08a4adc96ad --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Http/JsonAuthenticationSuccessHandler.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Http; + +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; + +class JsonAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface +{ + public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response + { + return new JsonResponse(['message' => sprintf('Good game @%s!', $token->getUserIdentifier())]); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/LoginFormAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/LoginFormAuthenticator.php index 1004ee2c10ba7..963be4141d27e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/LoginFormAuthenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/LoginFormAuthenticator.php @@ -16,11 +16,11 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\SecurityRequestAttributes; use Symfony\Component\Security\Http\Util\TargetPathTrait; class LoginFormAuthenticator extends AbstractLoginFormAuthenticator @@ -39,7 +39,7 @@ public function authenticate(Request $request): Passport { $username = $request->request->get('_username', ''); - $request->getSession()->set(Security::LAST_USERNAME, $username); + $request->getSession()->set(SecurityRequestAttributes::LAST_USERNAME, $username); return new Passport( new UserBadge($username), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php index c77b1e204e0db..22f7de69b208c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php @@ -51,9 +51,6 @@ public function secureAction() throw new \Exception('Wrapper', 0, new \Exception('Another Wrapper', 0, new AccessDeniedException())); } - /** - * {@inheritdoc} - */ public static function getSubscribedServices(): array { return [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php index 63b27512d5d27..0f89b3c1b4fc8 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php @@ -18,7 +18,7 @@ use Symfony\Component\Form\FormEvents; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Http\SecurityRequestAttributes; /** * Form type for use with the Security component's form-based authentication @@ -36,9 +36,6 @@ public function __construct(RequestStack $requestStack) $this->requestStack = $requestStack; } - /** - * {@inheritdoc} - */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder @@ -55,10 +52,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) * session for an authentication error and last username. */ $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($request) { - if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { - $error = $request->attributes->get(Security::AUTHENTICATION_ERROR); + if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $error = $request->attributes->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } else { - $error = $request->getSession()->get(Security::AUTHENTICATION_ERROR); + $error = $request->getSession()->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } if ($error) { @@ -66,14 +63,11 @@ public function buildForm(FormBuilderInterface $builder, array $options) } $event->setData(array_replace((array) $event->getData(), [ - 'username' => $request->getSession()->get(Security::LAST_USERNAME), + 'username' => $request->getSession()->get(SecurityRequestAttributes::LAST_USERNAME), ])); }); } - /** - * {@inheritdoc} - */ public function configureOptions(OptionsResolver $resolver) { /* Note: the form's csrf_token_id must correspond to that for the form login diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php index 11d00e257e98a..a09ca835897fd 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php @@ -14,7 +14,7 @@ use Psr\Container\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Http\SecurityRequestAttributes; use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; @@ -30,15 +30,15 @@ public function __construct(ContainerInterface $container) public function loginAction(Request $request) { // get the login error if there is one - if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { - $error = $request->attributes->get(Security::AUTHENTICATION_ERROR); + if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $error = $request->attributes->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } else { - $error = $request->getSession()->get(Security::AUTHENTICATION_ERROR); + $error = $request->getSession()->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } return new Response($this->container->get('twig')->render('@FormLogin/Localized/login.html.twig', [ // last username entered by the user - 'last_username' => $request->getSession()->get(Security::LAST_USERNAME), + 'last_username' => $request->getSession()->get(SecurityRequestAttributes::LAST_USERNAME), 'error' => $error, ])); } @@ -68,9 +68,6 @@ public function homepageAction() return (new Response('Homepage'))->setPublic(); } - /** - * {@inheritdoc} - */ public static function getSubscribedServices(): array { return [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php index db6aacca8cfc2..09b0ff09ba8d1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php @@ -15,8 +15,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\SecurityRequestAttributes; use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; @@ -32,15 +32,15 @@ public function __construct(ContainerInterface $container) public function loginAction(Request $request, UserInterface $user = null) { // get the login error if there is one - if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { - $error = $request->attributes->get(Security::AUTHENTICATION_ERROR); + if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $error = $request->attributes->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } else { - $error = $request->getSession()->get(Security::AUTHENTICATION_ERROR); + $error = $request->getSession()->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } return new Response($this->container->get('twig')->render('@FormLogin/Login/login.html.twig', [ // last username entered by the user - 'last_username' => $request->getSession()->get(Security::LAST_USERNAME), + 'last_username' => $request->getSession()->get(SecurityRequestAttributes::LAST_USERNAME), 'error' => $error, ])); } @@ -60,9 +60,6 @@ public function secureAction() throw new \Exception('Wrapper', 0, new \Exception('Another Wrapper', 0, new AccessDeniedException())); } - /** - * {@inheritdoc} - */ public static function getSubscribedServices(): array { return [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/FormLoginBundle.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/FormLoginBundle.php index c330723adff15..62490a739bdcc 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/FormLoginBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/FormLoginBundle.php @@ -18,9 +18,6 @@ class FormLoginBundle extends Bundle { - /** - * {@inheritdoc} - */ public function build(ContainerBuilder $container) { parent::build($container); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/StaticTokenProvider.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/StaticTokenProvider.php index 24ff10a4d0199..8a669e99bc48d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/StaticTokenProvider.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RememberMeBundle/Security/StaticTokenProvider.php @@ -23,8 +23,8 @@ class StaticTokenProvider implements TokenProviderInterface public function __construct($kernel) { // only reset the "internal db" for new tests - if (self::$kernelClass !== \get_class($kernel)) { - self::$kernelClass = \get_class($kernel); + if (self::$kernelClass !== $kernel::class) { + self::$kernelClass = $kernel::class; self::$db = []; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Security/Core/User/ArrayUserProvider.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Security/Core/User/ArrayUserProvider.php index 68f896bcc68b1..42f2df9e10043 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Security/Core/User/ArrayUserProvider.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Security/Core/User/ArrayUserProvider.php @@ -64,7 +64,7 @@ public function refreshUser(UserInterface $user): UserInterface } $storedUser = $this->getUser($user->getUserIdentifier()); - $class = \get_class($storedUser); + $class = $storedUser::class; return new $class($storedUser->getUserIdentifier(), $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled()); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php index 0af3fc2cd6c6b..78b7f6140542a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php @@ -132,7 +132,7 @@ public function testLoginThrottling() break; case 1: // Second attempt : login throttling ! - $this->assertStringContainsString('Too many failed login attempts, please try again in 8 minutes.', $text, 'Invalid response on 2nd attempt'); + $this->assertStringContainsString('Too many failed login attempts, please try again', $text, 'Invalid response on 2nd attempt'); break; case 2: // Third attempt with unexisting username @@ -140,7 +140,7 @@ public function testLoginThrottling() break; case 3: // Fourth attempt : still login throttling ! - $this->assertStringContainsString('Too many failed login attempts, please try again in 8 minutes.', $text, 'Invalid response on 4th attempt'); + $this->assertStringContainsString('Too many failed login attempts, please try again', $text, 'Invalid response on 4th attempt'); break; } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php index 76c5e807232b7..91eaefa45e076 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php @@ -11,7 +11,15 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\SecuredPageBundle\Security\Core\User\ArrayUserProvider; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; @@ -33,6 +41,10 @@ public function testServiceIsFunctional() $security = $container->get('functional_test.security.helper'); $this->assertTrue($security->isGranted('ROLE_USER')); $this->assertSame($token, $security->getToken()); + $request = new Request(); + $request->server->set('REQUEST_URI', '/main/foo'); + $this->assertInstanceOf(FirewallConfig::class, $firewallConfig = $security->getFirewallConfig($request)); + $this->assertSame('main', $firewallConfig->getName()); } /** @@ -77,6 +89,77 @@ public function userWillBeMarkedAsChangedIfRolesHasChangedProvider() ], ]; } + + /** + * @testWith ["form_login"] + * ["Symfony\\Bundle\\SecurityBundle\\Tests\\Functional\\Bundle\\AuthenticatorBundle\\ApiAuthenticator"] + */ + public function testLogin(string $authenticator) + { + $client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml', 'debug' > true]); + static::getContainer()->get(ForceLoginController::class)->authenticator = $authenticator; + $client->request('GET', '/main/force-login'); + $response = $client->getResponse(); + + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(['message' => 'Welcome @chalasr!'], json_decode($response->getContent(), true)); + $this->assertSame('chalasr', static::getContainer()->get('security.helper')->getUser()->getUserIdentifier()); + } + + public function testLogout() + { + $client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config.yml', 'debug' => true]); + $client->loginUser(new InMemoryUser('chalasr', 'the-password', ['ROLE_FOO']), 'main'); + + $client->request('GET', '/main/force-logout'); + $response = $client->getResponse(); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertNull(static::getContainer()->get('security.helper')->getUser()); + $this->assertSame(['message' => 'Logout successful'], json_decode($response->getContent(), true)); + } + + public function testLogoutWithCsrf() + { + $client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config_logout_csrf.yml', 'debug' => true]); + $client->loginUser(new InMemoryUser('chalasr', 'the-password', ['ROLE_FOO']), 'main'); + + // put a csrf token in the storage + /** @var EventDispatcherInterface $eventDispatcher */ + $eventDispatcher = static::getContainer()->get(EventDispatcherInterface::class); + $setCsrfToken = function (RequestEvent $event) { + static::getContainer()->get('security.csrf.token_storage')->setToken('logout', 'bar'); + $event->setResponse(new Response('')); + }; + $eventDispatcher->addListener(KernelEvents::REQUEST, $setCsrfToken); + try { + $client->request('GET', '/'.uniqid('', true)); + } finally { + $eventDispatcher->removeListener(KernelEvents::REQUEST, $setCsrfToken); + } + + static::getContainer()->get(LogoutController::class)->checkCsrf = true; + $client->request('GET', '/main/force-logout', ['_csrf_token' => 'bar']); + $response = $client->getResponse(); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertNull(static::getContainer()->get('security.helper')->getUser()); + $this->assertSame(['message' => 'Logout successful'], json_decode($response->getContent(), true)); + } + + public function testLogoutBypassCsrf() + { + $client = $this->createClient(['test_case' => 'SecurityHelper', 'root_config' => 'config_logout_csrf.yml']); + $client->loginUser(new InMemoryUser('chalasr', 'the-password', ['ROLE_FOO']), 'main'); + + $client->request('GET', '/main/force-logout'); + $response = $client->getResponse(); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertNull(static::getContainer()->get('security.helper')->getUser()); + $this->assertSame(['message' => 'Logout successful'], json_decode($response->getContent(), true)); + } } final class UserWithoutEquatable implements UserInterface, PasswordAuthenticatedUserInterface @@ -109,33 +192,21 @@ public function __toString(): string return $this->getUserIdentifier(); } - /** - * {@inheritdoc} - */ public function getRoles(): array { return $this->roles; } - /** - * {@inheritdoc} - */ public function getPassword(): ?string { return $this->password; } - /** - * {@inheritdoc} - */ public function getSalt(): string { return ''; } - /** - * {@inheritdoc} - */ public function getUsername(): string { return $this->username; @@ -146,42 +217,68 @@ public function getUserIdentifier(): string return $this->username; } - /** - * {@inheritdoc} - */ public function isAccountNonExpired(): bool { return $this->accountNonExpired; } - /** - * {@inheritdoc} - */ public function isAccountNonLocked(): bool { return $this->accountNonLocked; } - /** - * {@inheritdoc} - */ public function isCredentialsNonExpired(): bool { return $this->credentialsNonExpired; } - /** - * {@inheritdoc} - */ public function isEnabled(): bool { return $this->enabled; } - /** - * {@inheritdoc} - */ public function eraseCredentials(): void { } } + +class ForceLoginController +{ + public $authenticator = 'form_login'; + + public function __construct(private Security $security) + { + } + + public function welcome() + { + $user = new InMemoryUser('chalasr', 'the-password', ['ROLE_FOO']); + $this->security->login($user, $this->authenticator); + + return new JsonResponse(['message' => sprintf('Welcome @%s!', $this->security->getUser()->getUserIdentifier())]); + } +} + +class LogoutController +{ + public $checkCsrf = false; + + public function __construct(private Security $security) + { + } + + public function logout(UserInterface $user) + { + $this->security->logout($this->checkCsrf); + + return new JsonResponse(['message' => 'Logout successful']); + } +} + +class LoggedInController +{ + public function __invoke(UserInterface $user) + { + return new JsonResponse(['message' => sprintf('Welcome back @%s', $user->getUserIdentifier())]); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AbstractTokenCompareRoles/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AbstractTokenCompareRoles/config.yml index 1cc13de77736f..54bfaf89cb6c7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AbstractTokenCompareRoles/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AbstractTokenCompareRoles/config.yml @@ -8,8 +8,6 @@ services: class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\SecuredPageBundle\Security\Core\User\ArrayUserProvider security: - enable_authenticator_manager: true - password_hashers: \Symfony\Component\Security\Core\User\UserInterface: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/bundles.php new file mode 100644 index 0000000000000..ea92c9159102d --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/bundles.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + new Symfony\Bundle\SecurityBundle\SecurityBundle(), + new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\AccessTokenBundle(), + new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\TestBundle(), +]; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_anonymous.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_anonymous.yml new file mode 100644 index 0000000000000..ae177caf8b3bf --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_anonymous.yml @@ -0,0 +1,32 @@ +imports: + - { resource: ./../config/framework.yml } + +framework: + http_method_override: false + serializer: ~ + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + lazy: true + access_token: + token_handler: access_token.access_token_handler + token_extractors: 'security.access_token_extractor.header' + + access_control: + - { path: ^/foo, roles: ROLE_USER } + - { path: ^/bar, roles: PUBLIC_ACCESS } + +services: + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_body_custom.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_body_custom.yml new file mode 100644 index 0000000000000..9c6a203331611 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_body_custom.yml @@ -0,0 +1,36 @@ +imports: + - { resource: ./../config/framework.yml } + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + token_handler: access_token.access_token_handler + success_handler: access_token.success_handler + failure_handler: access_token.failure_handler + token_extractors: 'custom_extractor' + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + custom_extractor: + class: Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor + arguments: + - 'secured_token' + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler + access_token.success_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Http\JsonAuthenticationSuccessHandler + access_token.failure_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Http\JsonAuthenticationFailureHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_body_default.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_body_default.yml new file mode 100644 index 0000000000000..c1a7a8e7b6ba8 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_body_default.yml @@ -0,0 +1,31 @@ +imports: + - { resource: ./../config/framework.yml } + +framework: + http_method_override: false + serializer: ~ + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + token_handler: access_token.access_token_handler + token_extractors: 'request_body' + realm: 'My API' + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_header_custom.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_header_custom.yml new file mode 100644 index 0000000000000..befc881f3fb94 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_header_custom.yml @@ -0,0 +1,37 @@ +imports: + - { resource: ./../config/framework.yml } + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + token_handler: access_token.access_token_handler + success_handler: access_token.success_handler + failure_handler: access_token.failure_handler + token_extractors: 'custom_extractor' + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + custom_extractor: + class: Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor + arguments: + - 'X-AUTH-TOKEN' + - '' + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler + access_token.success_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Http\JsonAuthenticationSuccessHandler + access_token.failure_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Http\JsonAuthenticationFailureHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_header_default.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_header_default.yml new file mode 100644 index 0000000000000..8dea7ec2ab8da --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_header_default.yml @@ -0,0 +1,31 @@ +imports: + - { resource: ./../config/framework.yml } + +framework: + http_method_override: false + serializer: ~ + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + token_handler: access_token.access_token_handler + token_extractors: 'header' + realm: 'My API' + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_multiple_extractors.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_multiple_extractors.yml new file mode 100644 index 0000000000000..a90114f80ac72 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_multiple_extractors.yml @@ -0,0 +1,33 @@ +imports: + - { resource: ./../config/framework.yml } + +framework: + http_method_override: false + serializer: ~ + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + token_handler: access_token.access_token_handler + token_extractors: + - 'security.access_token_extractor.query_string' + - 'security.access_token_extractor.request_body' + - 'security.access_token_extractor.header' + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_no_extractors.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_no_extractors.yml new file mode 100644 index 0000000000000..41f1d96e5108b --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_no_extractors.yml @@ -0,0 +1,26 @@ +imports: + - { resource: ./../config/framework.yml } + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + token_handler: access_token.access_token_handler + token_extractors: [] + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_no_handler.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_no_handler.yml new file mode 100644 index 0000000000000..f3895738680af --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_no_handler.yml @@ -0,0 +1,28 @@ +imports: + - { resource: ./../config/framework.yml } + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + success_handler: access_token.success_handler + failure_handler: access_token.failure_handler + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + access_token.success_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Http\JsonAuthenticationSuccessHandler + access_token.failure_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Http\JsonAuthenticationFailureHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_query_custom.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_query_custom.yml new file mode 100644 index 0000000000000..b41be357482ae --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_query_custom.yml @@ -0,0 +1,36 @@ +imports: + - { resource: ./../config/framework.yml } + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + token_handler: access_token.access_token_handler + success_handler: access_token.success_handler + failure_handler: access_token.failure_handler + token_extractors: 'custom_extractor' + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + custom_extractor: + class: Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor + arguments: + - 'protection_token' + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler + access_token.success_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Http\JsonAuthenticationSuccessHandler + access_token.failure_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Http\JsonAuthenticationFailureHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_query_default.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_query_default.yml new file mode 100644 index 0000000000000..16256e32c9e69 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/config_query_default.yml @@ -0,0 +1,31 @@ +imports: + - { resource: ./../config/framework.yml } + +framework: + http_method_override: false + serializer: ~ + +security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + + providers: + in_memory: + memory: + users: + dunglas: { password: foo, roles: [ROLE_USER] } + + firewalls: + main: + pattern: ^/ + access_token: + token_handler: access_token.access_token_handler + token_extractors: 'query_string' + realm: 'My API' + + access_control: + - { path: ^/foo, roles: ROLE_USER } + +services: + access_token.access_token_handler: + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Security\Handler\AccessTokenHandler diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/routing.yml new file mode 100644 index 0000000000000..cbf300db53ba2 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AccessToken/routing.yml @@ -0,0 +1,6 @@ +foo_route: + path: /foo + defaults: { _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Controller\FooController::__invoke } +bar_route: + path: /bar + defaults: { _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AccessTokenBundle\Controller\BarController::__invoke } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php index 2839d5dfaff60..d10326e676a38 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php @@ -46,9 +46,6 @@ public function __construct($varDir, $testCase, $rootConfig, $environment, $debu parent::__construct($environment, $debug); } - /** - * {@inheritdoc} - */ public function getContainerClass(): string { return parent::getContainerClass().substr(md5(implode('', $this->rootConfig)), -16); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/firewall_user_provider.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/firewall_user_provider.yml index 7822396eae16a..4fb5ce880aacd 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/firewall_user_provider.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/firewall_user_provider.yml @@ -3,7 +3,6 @@ imports: - { resource: ./security.yml } security: - enable_authenticator_manager: true firewalls: api: pattern: / diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/implicit_user_provider.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/implicit_user_provider.yml index b2433ecd35a1c..1cb8b0c6786e7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/implicit_user_provider.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/implicit_user_provider.yml @@ -3,7 +3,6 @@ imports: - { resource: ./security.yml } security: - enable_authenticator_manager: true firewalls: api: pattern: / diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/multiple_firewalls.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/multiple_firewalls.yml index 655a8d83d4d5f..b424be8438419 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/multiple_firewalls.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/multiple_firewalls.yml @@ -3,7 +3,6 @@ imports: - { resource: ./security.yml } security: - enable_authenticator_manager: true firewalls: firewall1: pattern: /firewall1 diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/no_user_provider.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/no_user_provider.yml index 3983d567c5572..6be03adad7283 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/no_user_provider.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/no_user_provider.yml @@ -6,8 +6,6 @@ services: - true security: - enable_authenticator_manager: true - firewalls: api: pattern: / diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/security.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/security.yml index 2a1d748ec2fb4..484760a536f60 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/security.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Authenticator/security.yml @@ -1,6 +1,4 @@ security: - enable_authenticator_manager: true - password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AutowiringTypes/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AutowiringTypes/config.yml index 8be3ebc6436af..2045118e1b9f1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AutowiringTypes/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AutowiringTypes/config.yml @@ -7,7 +7,6 @@ services: class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AutowiringBundle\AutowiredServices autowire: true security: - enable_authenticator_manager: true providers: dummy: memory: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml index 945fd0fce3366..069fece61756f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/base_config.yml @@ -15,7 +15,6 @@ services: - { name: container.service_subscriber } security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml index ff265cac9e27d..98ba0eb5326ad 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml @@ -2,7 +2,6 @@ imports: - { resource: ./base_config.yml } security: - enable_authenticator_manager: true firewalls: default: form_login: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/routes_as_path.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/routes_as_path.yml index 57abb3f2f6771..d481e6d2b7150 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/routes_as_path.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/routes_as_path.yml @@ -2,7 +2,6 @@ imports: - { resource: ./config.yml } security: - enable_authenticator_manager: true firewalls: default: form_login: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml index a613f5f24ca7e..00939adc182b3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml @@ -16,7 +16,6 @@ services: logger: { class: Psr\Log\NullLogger } security: - enable_authenticator_manager: true firewalls: secure: pattern: ^/secure/ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml index 2c80b9383bb28..022263a978e6d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml @@ -6,7 +6,6 @@ framework: serializer: ~ security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml index b8986de18f499..f1f1a93ab0c0b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml @@ -2,7 +2,6 @@ imports: - { resource: ./../config/framework.yml } security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/switchuser_stateless.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/switchuser_stateless.yml index e2bebd525f488..84a0493e050b2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/switchuser_stateless.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/switchuser_stateless.yml @@ -2,7 +2,6 @@ imports: - { resource: ./config.yml } security: - enable_authenticator_manager: true providers: in_memory: memory: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/config.yml index 67234430451db..5d4bc1bffcf7e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/config.yml @@ -12,7 +12,6 @@ services: protocol_version: 3 referrals: false security: - enable_authenticator_manager: true providers: ldap: ldap: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LoginLink/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LoginLink/config.yml index 969c669eabcfc..7feaca0cb88ce 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LoginLink/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LoginLink/config.yml @@ -2,8 +2,6 @@ imports: - { resource: ./../config/framework.yml } security: - enable_authenticator_manager: true - providers: in_memory: memory: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_access.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_access.yml index fbcb7e6defc79..31ecfb6897c42 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_access.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_access.yml @@ -2,8 +2,6 @@ imports: - { resource: ./../config/framework.yml } security: - enable_authenticator_manager: true - password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_cookie_clearing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_cookie_clearing.yml index 974f0ab79df64..2472cec31a437 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_cookie_clearing.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Logout/config_cookie_clearing.yml @@ -2,7 +2,6 @@ imports: - { resource: ./../config/framework.yml } security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml index 1dd8b8e507d36..f28924e4518d9 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml @@ -2,8 +2,6 @@ imports: - { resource: ./../config/framework.yml } security: - enable_authenticator_manager: true - password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/MissingUserProvider/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/MissingUserProvider/config.yml index ec3839a76adcb..501a673b4fdea 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/MissingUserProvider/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/MissingUserProvider/config.yml @@ -2,7 +2,6 @@ imports: - { resource: ./../config/framework.yml } security: - enable_authenticator_manager: true firewalls: default: http_basic: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config.yml index fe52f22500606..696a9041e8035 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config.yml @@ -2,7 +2,6 @@ imports: - { resource: ./../config/framework.yml } security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config_persistent.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config_persistent.yml index 40ded00c5539c..a529c217f2255 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config_persistent.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config_persistent.yml @@ -4,7 +4,6 @@ services: arguments: ['@kernel'] security: - enable_authenticator_manager: true firewalls: default: remember_me: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config_session.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config_session.yml index a11750e6f60be..411de7211ebce 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config_session.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/config_session.yml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true firewalls: default: remember_me: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/stateless_config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/stateless_config.yml index 31053f856373d..2a21c54c0b206 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/stateless_config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMe/stateless_config.yml @@ -9,7 +9,6 @@ framework: cookie_samesite: lax security: - enable_authenticator_manager: true firewalls: default: stateless: true diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeCookie/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeCookie/config.yml index 4df09bad41582..923e15e8dfd7e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeCookie/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeCookie/config.yml @@ -2,7 +2,6 @@ imports: - { resource: ./../config/framework.yml } security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml index f2ac6ebde364f..c6934ba011e94 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml @@ -2,7 +2,6 @@ imports: - { resource: ./../config/framework.yml } services: - # alias the service so we can access it in the tests functional_test.security.helper: alias: security.helper public: true @@ -11,12 +10,37 @@ services: alias: security.token_storage public: true + Symfony\Bundle\SecurityBundle\Tests\Functional\ForceLoginController: + arguments: ['@security.helper'] + public: true + + Symfony\Bundle\SecurityBundle\Tests\Functional\LogoutController: + arguments: ['@security.helper'] + public: true + + Symfony\Bundle\SecurityBundle\Tests\Functional\LoggedInController: + arguments: ['@security.helper'] + public: true + + Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator: ~ + security: - enable_authenticator_manager: true providers: - in_memory: + main: memory: - users: [] + users: + chalasr: { password: the-password, roles: ['ROLE_FOO'] } + no-role-username: { password: the-password, roles: [] } firewalls: - default: + main: + pattern: ^/main + form_login: + check_path: /main/login/check + custom_authenticators: + - 'Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator' + provider: main + + access_control: + - { path: '^/main/login/check$', roles: IS_AUTHENTICATED_FULLY } + - { path: '^/main/logged-in$', roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config_logout_csrf.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config_logout_csrf.yml new file mode 100644 index 0000000000000..c397abb9b1aa3 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config_logout_csrf.yml @@ -0,0 +1,46 @@ +imports: + - { resource: ./../config/framework.yml } + +services: + functional_test.security.helper: + alias: security.helper + public: true + + functional.test.security.token_storage: + alias: security.token_storage + public: true + + Symfony\Bundle\SecurityBundle\Tests\Functional\ForceLoginController: + arguments: ['@security.helper'] + public: true + + Symfony\Bundle\SecurityBundle\Tests\Functional\LogoutController: + arguments: ['@security.helper'] + public: true + + Symfony\Bundle\SecurityBundle\Tests\Functional\LoggedInController: + arguments: ['@security.helper'] + public: true + + Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator: ~ + +security: + providers: + main: + memory: + users: + chalasr: { password: the-password, roles: ['ROLE_FOO'] } + no-role-username: { password: the-password, roles: [] } + + firewalls: + main: + pattern: ^/main + form_login: + check_path: /main/login/check + custom_authenticators: + - 'Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\AuthenticatorBundle\ApiAuthenticator' + provider: main + + access_control: + - { path: '^/main/logged-in$', roles: IS_AUTHENTICATED_FULLY } + - { path: '^/main/force-logout$', roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/routing.yml new file mode 100644 index 0000000000000..1f74d27501207 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/routing.yml @@ -0,0 +1,11 @@ +force-login: + path: /main/force-login + controller: Symfony\Bundle\SecurityBundle\Tests\Functional\ForceLoginController::welcome + +logged-in: + path: /main/logged-in + controller: Symfony\Bundle\SecurityBundle\Tests\Functional\LoggedInController + +force-logout: + path: /main/force-logout + controller: Symfony\Bundle\SecurityBundle\Tests\Functional\LogoutController::logout diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/base_config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/base_config.yml index a243ec5f0a448..4260d8aa5545d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/base_config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/base_config.yml @@ -6,7 +6,6 @@ parameters: env(APP_IPS): '127.0.0.1, ::1' security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/invalid_ip_access_control.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/invalid_ip_access_control.yml index 0f190d9b6d1e4..6b57da1eab294 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/invalid_ip_access_control.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/invalid_ip_access_control.yml @@ -2,7 +2,6 @@ imports: - { resource: ./../config/default.yml } security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml index 95603e583534d..f1cddb0e7f92a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml @@ -2,7 +2,6 @@ imports: - { resource: ./../config/default.yml } security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml index 42f18b392d020..83ceaaac81a7c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml @@ -2,7 +2,6 @@ imports: - { resource: ./../config/default.yml } security: - enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\InMemoryUser: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes_with_forward.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes_with_forward.yml index 9cbfe5dae7159..12d90d8835858 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes_with_forward.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes_with_forward.yml @@ -2,7 +2,6 @@ imports: - { resource: ./localized_routes.yml } security: - enable_authenticator_manager: true firewalls: default: form_login: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/login_throttling.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/login_throttling.yml index 5f22f41780cd0..90e276f02d026 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/login_throttling.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/login_throttling.yml @@ -7,7 +7,6 @@ framework: rate_limiter: ~ security: - enable_authenticator_manager: true firewalls: default: login_throttling: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/routes_as_path.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/routes_as_path.yml index 435951968d773..fc31d7743a947 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/routes_as_path.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/routes_as_path.yml @@ -2,7 +2,6 @@ imports: - { resource: ./base_config.yml } security: - enable_authenticator_manager: true firewalls: default: form_login: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/switchuser.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/switchuser.yml index 4806ed5e0cb5d..bd6f56d2c74da 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/switchuser.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/switchuser.yml @@ -2,7 +2,6 @@ imports: - { resource: ./base_config.yml } security: - enable_authenticator_manager: true providers: in_memory: memory: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php new file mode 100644 index 0000000000000..7ee7a2465263d --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityTest.php @@ -0,0 +1,382 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\LogicException; +use Symfony\Component\Security\Core\User\InMemoryUser; +use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Event\LogoutEvent; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; + +class SecurityTest extends TestCase +{ + public function testGetToken() + { + $token = new UsernamePasswordToken(new InMemoryUser('foo', 'bar'), 'provider'); + $tokenStorage = $this->createMock(TokenStorageInterface::class); + + $tokenStorage->expects($this->once()) + ->method('getToken') + ->willReturn($token); + + $container = $this->createContainer('security.token_storage', $tokenStorage); + + $security = new Security($container); + $this->assertSame($token, $security->getToken()); + } + + /** + * @dataProvider getUserTests + */ + public function testGetUser($userInToken, $expectedUser) + { + $token = $this->createMock(TokenInterface::class); + $token->expects($this->any()) + ->method('getUser') + ->willReturn($userInToken); + $tokenStorage = $this->createMock(TokenStorageInterface::class); + + $tokenStorage->expects($this->once()) + ->method('getToken') + ->willReturn($token); + + $container = $this->createContainer('security.token_storage', $tokenStorage); + + $security = new Security($container); + $this->assertSame($expectedUser, $security->getUser()); + } + + public function getUserTests() + { + yield [null, null]; + + $user = new InMemoryUser('nice_user', 'foo'); + yield [$user, $user]; + } + + public function testIsGranted() + { + $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); + + $authorizationChecker->expects($this->once()) + ->method('isGranted') + ->with('SOME_ATTRIBUTE', 'SOME_SUBJECT') + ->willReturn(true); + + $container = $this->createContainer('security.authorization_checker', $authorizationChecker); + + $security = new Security($container); + $this->assertTrue($security->isGranted('SOME_ATTRIBUTE', 'SOME_SUBJECT')); + } + + /** + * @dataProvider getFirewallConfigTests + */ + public function testGetFirewallConfig(Request $request, ?FirewallConfig $expectedFirewallConfig) + { + $firewallMap = $this->createMock(FirewallMap::class); + + $firewallMap->expects($this->once()) + ->method('getFirewallConfig') + ->with($request) + ->willReturn($expectedFirewallConfig); + + $container = $this->createContainer('security.firewall.map', $firewallMap); + + $security = new Security($container); + $this->assertSame($expectedFirewallConfig, $security->getFirewallConfig($request)); + } + + public function getFirewallConfigTests() + { + $request = new Request(); + + yield [$request, null]; + yield [$request, new FirewallConfig('main', 'acme_user_checker')]; + } + + public function testLogin() + { + $request = new Request(); + $authenticator = $this->createMock(AuthenticatorInterface::class); + $requestStack = $this->createMock(RequestStack::class); + $firewallMap = $this->createMock(FirewallMap::class); + $firewall = new FirewallConfig('main', 'main'); + $userAuthenticator = $this->createMock(UserAuthenticatorInterface::class); + $user = $this->createMock(UserInterface::class); + $userChecker = $this->createMock(UserCheckerInterface::class); + + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['request_stack', $requestStack], + ['security.firewall.map', $firewallMap], + ['security.user_authenticator', $userAuthenticator], + ['security.user_checker', $userChecker], + ]) + ; + + $requestStack->expects($this->once())->method('getCurrentRequest')->willReturn($request); + $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewall); + $userAuthenticator->expects($this->once())->method('authenticateUser')->with($user, $authenticator, $request); + $userChecker->expects($this->once())->method('checkPreAuth')->with($user); + + $firewallAuthenticatorLocator = $this->createMock(ServiceProviderInterface::class); + $firewallAuthenticatorLocator + ->expects($this->once()) + ->method('getProvidedServices') + ->willReturn(['security.authenticator.custom.dev' => $authenticator]) + ; + $firewallAuthenticatorLocator + ->expects($this->once()) + ->method('get') + ->with('security.authenticator.custom.dev') + ->willReturn($authenticator) + ; + + $security = new Security($container, ['main' => $firewallAuthenticatorLocator]); + + $security->login($user); + } + + public function testLogout() + { + $request = new Request(); + $requestStack = $this->createMock(RequestStack::class); + $requestStack->expects($this->once())->method('getMainRequest')->willReturn($request); + + $token = $this->createMock(TokenInterface::class); + $token->method('getUser')->willReturn(new InMemoryUser('foo', 'bar')); + $tokenStorage = $this->createMock(TokenStorageInterface::class); + $tokenStorage->expects($this->once())->method('getToken')->willReturn($token); + $tokenStorage->expects($this->once())->method('setToken')->with(null); + + $eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $eventDispatcher + ->expects($this->once()) + ->method('dispatch') + ->with(new LogoutEvent($request, $token)) + ; + + $firewallMap = $this->createMock(FirewallMap::class); + $firewallConfig = new FirewallConfig('my_firewall', 'user_checker'); + $firewallMap + ->expects($this->once()) + ->method('getFirewallConfig') + ->willReturn($firewallConfig) + ; + + $eventDispatcherLocator = $this->createMock(ContainerInterface::class); + $eventDispatcherLocator + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['my_firewall', $eventDispatcher], + ]) + ; + + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['request_stack', $requestStack], + ['security.token_storage', $tokenStorage], + ['security.firewall.map', $firewallMap], + ['security.firewall.event_dispatcher_locator', $eventDispatcherLocator], + ]) + ; + $security = new Security($container); + $security->logout(false); + } + + public function testLogoutWithoutFirewall() + { + $request = new Request(); + $requestStack = $this->createMock(RequestStack::class); + $requestStack->expects($this->once())->method('getMainRequest')->willReturn($request); + + $token = $this->createMock(TokenInterface::class); + $token->method('getUser')->willReturn(new InMemoryUser('foo', 'bar')); + $tokenStorage = $this->createMock(TokenStorageInterface::class); + $tokenStorage + ->expects($this->once()) + ->method('getToken') + ->willReturn($token) + ; + + $firewallMap = $this->createMock(FirewallMap::class); + $firewallMap + ->expects($this->once()) + ->method('getFirewallConfig') + ->willReturn(null) + ; + + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['request_stack', $requestStack], + ['security.token_storage', $tokenStorage], + ['security.firewall.map', $firewallMap], + ]) + ; + + $this->expectException(LogicException::class); + $security = new Security($container); + $security->logout(false); + } + + public function testLogoutWithResponse() + { + $request = new Request(); + $requestStack = $this->createMock(RequestStack::class); + $requestStack->expects($this->once())->method('getMainRequest')->willReturn($request); + + $token = $this->createMock(TokenInterface::class); + $token->method('getUser')->willReturn(new InMemoryUser('foo', 'bar')); + $tokenStorage = $this->createMock(TokenStorageInterface::class); + $tokenStorage->expects($this->once())->method('getToken')->willReturn($token); + $tokenStorage->expects($this->once())->method('setToken')->with(null); + + $eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $eventDispatcher + ->expects($this->once()) + ->method('dispatch') + ->willReturnCallback(function ($event) use ($request, $token) { + $this->assertInstanceOf(LogoutEvent::class, $event); + $this->assertEquals($request, $event->getRequest()); + $this->assertEquals($token, $event->getToken()); + + $event->setResponse(new Response('a custom response')); + + return $event; + }) + ; + + $firewallMap = $this->createMock(FirewallMap::class); + $firewallConfig = new FirewallConfig('my_firewall', 'user_checker'); + $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewallConfig); + + $eventDispatcherLocator = $this->createMock(ContainerInterface::class); + $eventDispatcherLocator + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([['my_firewall', $eventDispatcher]]) + ; + + $container = $this->createMock(ContainerInterface::class); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['request_stack', $requestStack], + ['security.token_storage', $tokenStorage], + ['security.firewall.map', $firewallMap], + ['security.firewall.event_dispatcher_locator', $eventDispatcherLocator], + ]) + ; + $security = new Security($container); + $response = $security->logout(false); + + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals('a custom response', $response->getContent()); + } + + public function testLogoutWithValidCsrf() + { + $request = new Request(['_csrf_token' => 'dummytoken']); + $requestStack = $this->createMock(RequestStack::class); + $requestStack->expects($this->once())->method('getMainRequest')->willReturn($request); + + $token = $this->createMock(TokenInterface::class); + $token->method('getUser')->willReturn(new InMemoryUser('foo', 'bar')); + $tokenStorage = $this->createMock(TokenStorageInterface::class); + $tokenStorage->expects($this->once())->method('getToken')->willReturn($token); + $tokenStorage->expects($this->once())->method('setToken')->with(null); + + $eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $eventDispatcher + ->expects($this->once()) + ->method('dispatch') + ->willReturnCallback(function ($event) use ($request, $token) { + $this->assertInstanceOf(LogoutEvent::class, $event); + $this->assertEquals($request, $event->getRequest()); + $this->assertEquals($token, $event->getToken()); + + $event->setResponse(new Response('a custom response')); + + return $event; + }) + ; + + $firewallMap = $this->createMock(FirewallMap::class); + $firewallConfig = new FirewallConfig(name: 'my_firewall', userChecker: 'user_checker', logout: ['csrf_parameter' => '_csrf_token', 'csrf_token_id' => 'logout']); + $firewallMap->expects($this->once())->method('getFirewallConfig')->willReturn($firewallConfig); + + $eventDispatcherLocator = $this->createMock(ContainerInterface::class); + $eventDispatcherLocator + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([['my_firewall', $eventDispatcher]]) + ; + + $csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class); + $csrfTokenManager->expects($this->once())->method('isTokenValid')->with($this->equalTo(new CsrfToken('logout', 'dummytoken')))->willReturn(true); + + $container = $this->createMock(ContainerInterface::class); + $container->expects($this->once())->method('has')->with('security.csrf.token_manager')->willReturn(true); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap([ + ['request_stack', $requestStack], + ['security.token_storage', $tokenStorage], + ['security.firewall.map', $firewallMap], + ['security.firewall.event_dispatcher_locator', $eventDispatcherLocator], + ['security.csrf.token_manager', $csrfTokenManager], + ]) + ; + $security = new Security($container); + $response = $security->logout(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals('a custom response', $response->getContent()); + } + + private function createContainer(string $serviceId, object $serviceObject): ContainerInterface + { + return new ServiceLocator([$serviceId => fn () => $serviceObject]); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 9c3db8d752dc6..f16293d3939ce 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -19,15 +19,15 @@ "php": ">=8.1", "composer-runtime-api": ">=2.1", "ext-xml": "*", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.2", "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", + "symfony/http-foundation": "^6.2", "symfony/password-hasher": "^5.4|^6.0", - "symfony/security-core": "^5.4|^6.0", + "symfony/security-core": "^6.2", "symfony/security-csrf": "^5.4|^6.0", - "symfony/security-http": "^5.4|^6.0" + "symfony/security-http": "^6.2" }, "require-dev": { "doctrine/annotations": "^1.10.4", diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md index aaa0ae400f7a1..5502812c27ec1 100644 --- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +6.2 +--- + + * Add the `twig.mailer.html_to_text_converter` option to allow configuring custom `HtmlToTextConverterInterface` + implementations to be used by the `twig.mime_body_renderer` service + 6.1 --- diff --git a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php index 63d0227513845..d26ddf358aaba 100644 --- a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php +++ b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php @@ -36,8 +36,6 @@ public function __construct(ContainerInterface $container, iterable $iterator) } /** - * {@inheritdoc} - * * @return string[] A list of template files to preload on PHP 7.4+ */ public function warmUp(string $cacheDir): array @@ -68,17 +66,11 @@ public function warmUp(string $cacheDir): array return $files; } - /** - * {@inheritdoc} - */ public function isOptional(): bool { return true; } - /** - * {@inheritdoc} - */ public static function getSubscribedServices(): array { return [ diff --git a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php index 5de2b2aad1358..f85dd13aa2f3a 100644 --- a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php +++ b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php @@ -23,9 +23,6 @@ #[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] final class LintCommand extends BaseLintCommand { - /** - * {@inheritdoc} - */ protected function configure() { parent::configure(); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php index 45413dc93253d..3a90bce15ffaa 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php @@ -50,7 +50,7 @@ public function process(ContainerBuilder $container) } } - if (!empty($twigBridgeExtensionsMethodCalls) || !empty($othersExtensionsMethodCalls)) { + if ($twigBridgeExtensionsMethodCalls || $othersExtensionsMethodCalls) { $definition->setMethodCalls(array_merge($twigBridgeExtensionsMethodCalls, $othersExtensionsMethodCalls, $currentMethodCalls)); } } diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index b85a3bf4b1958..0655a51e7c159 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -15,6 +15,7 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; /** * TwigExtension configuration structure. @@ -48,6 +49,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addGlobalsSection($rootNode); $this->addTwigOptions($rootNode); $this->addTwigFormatOptions($rootNode); + $this->addMailerSection($rootNode); return $treeBuilder; } @@ -213,4 +215,20 @@ private function addTwigFormatOptions(ArrayNodeDefinition $rootNode) ->end() ; } + + private function addMailerSection(ArrayNodeDefinition $rootNode) + { + $rootNode + ->children() + ->arrayNode('mailer') + ->children() + ->scalarNode('html_to_text_converter') + ->info(sprintf('A service implementing the "%s"', HtmlToTextConverterInterface::class)) + ->defaultNull() + ->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index f598524da4d64..b323bf808946e 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -54,10 +54,6 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('console.php'); } - if ($container::willBeAvailable('symfony/mailer', Mailer::class, ['symfony/twig-bundle'])) { - $loader->load('mailer.php'); - } - if (!$container::willBeAvailable('symfony/translation', Translator::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.translation.extractor'); } @@ -79,6 +75,14 @@ public function load(array $configs, ContainerBuilder $container) $config = $this->processConfiguration($configuration, $configs); + if ($container::willBeAvailable('symfony/mailer', Mailer::class, ['symfony/twig-bundle'])) { + $loader->load('mailer.php'); + + if ($htmlToTextConverter = $config['mailer']['html_to_text_converter'] ?? null) { + $container->getDefinition('twig.mime_body_renderer')->setArgument('$converter', new Reference($htmlToTextConverter)); + } + } + $container->setParameter('twig.form.resources', $config['form_themes']); $container->setParameter('twig.default_path', $config['default_path']); $defaultTwigPath = $container->getParameterBag()->resolveValue($config['default_path']); @@ -194,9 +198,6 @@ private function normalizeBundleName(string $name): string return $name; } - /** - * {@inheritdoc} - */ public function getXsdValidationBasePath(): string|false { return __DIR__.'/../Resources/config/schema'; diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd b/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd index 429c91db67b74..50eff2bc29923 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd @@ -14,6 +14,7 @@ + @@ -55,4 +56,8 @@
+ + + + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php index 5536315306b72..aa5c543b30f40 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Twig\AppVariable; use Symfony\Bridge\Twig\DataCollector\TwigDataCollector; use Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer; +use Symfony\Bridge\Twig\EventListener\TemplateAttributeListener; use Symfony\Bridge\Twig\Extension\AssetExtension; use Symfony\Bridge\Twig\Extension\CodeExtension; use Symfony\Bridge\Twig\Extension\ExpressionExtension; @@ -140,7 +141,7 @@ ->tag('translation.extractor', ['alias' => 'twig']) ->set('workflow.twig_extension', WorkflowExtension::class) - ->args([service('workflow.registry')]) + ->args([service('.workflow.registry')]) ->set('twig.configurator.environment', EnvironmentConfigurator::class) ->args([ @@ -169,5 +170,9 @@ ->args([service('serializer')]) ->set('twig.extension.serializer', SerializerExtension::class) + + ->set('controller.template_attribute_listener', TemplateAttributeListener::class) + ->args([service('twig')]) + ->tag('kernel.event_subscriber') ; }; diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/mailer.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/mailer.php new file mode 100644 index 0000000000000..2404f221ad680 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/mailer.php @@ -0,0 +1,7 @@ +loadFromExtension('twig', [ + 'mailer' => [ + 'html_to_text_converter' => 'my_converter', + ], +]); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml new file mode 100644 index 0000000000000..25c2a9453663e --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml new file mode 100644 index 0000000000000..139e2077280ba --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml @@ -0,0 +1,3 @@ +twig: + mailer: + html_to_text_converter: 'my_converter' diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index cd60cbb7fd9e3..bfb215b488715 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -24,6 +24,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\Mailer\Mailer; class TwigExtensionTest extends TestCase { @@ -43,6 +44,10 @@ public function testLoadEmptyConfiguration() $this->assertEquals('%kernel.cache_dir%/twig', $options['cache'], '->load() sets default value for cache option'); $this->assertEquals('%kernel.charset%', $options['charset'], '->load() sets default value for charset option'); $this->assertEquals('%kernel.debug%', $options['debug'], '->load() sets default value for debug option'); + + if (class_exists(Mailer::class)) { + $this->assertCount(1, $container->getDefinition('twig.mime_body_renderer')->getArguments()); + } } /** @@ -263,6 +268,25 @@ public function testRuntimeLoader() $this->assertEquals('foo', $args['FooClass']->getValues()[0]); } + /** + * @dataProvider getFormats + */ + public function testCustomHtmlToTextConverterService(string $format) + { + if (!class_exists(Mailer::class)) { + $this->markTestSkipped('The "twig.mime_body_renderer" service requires the Mailer component'); + } + + $container = $this->createContainer(); + $container->registerExtension(new TwigExtension()); + $this->loadFromFile($container, 'mailer', $format); + $this->compileContainer($container); + + $bodyRenderer = $container->getDefinition('twig.mime_body_renderer'); + $this->assertCount(2, $bodyRenderer->getArguments()); + $this->assertEquals(new Reference('my_converter'), $bodyRenderer->getArgument('$converter')); + } + private function createContainer() { $container = new ContainerBuilder(new ParameterBag([ diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 379d95c2fd3cb..b587875e2529b 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -18,12 +18,11 @@ "require": { "php": ">=8.1", "composer-runtime-api": ">=2.1", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/twig-bridge": "^5.4|^6.0", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.1", + "symfony/twig-bridge": "^6.2", "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/polyfill-ctype": "~1.8", + "symfony/http-kernel": "^6.2", "twig/twig": "^2.13|^3.0.4" }, "require-dev": { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index 3cdec828217b5..3c6ec28523aed 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -113,7 +113,7 @@ public function panelAction(Request $request, string $token): Response 'request' => $request, 'templates' => $this->getTemplateManager()->getNames($profile), 'is_ajax' => $request->isXmlHttpRequest(), - 'profiler_markup_version' => 2, // 1 = original profiler, 2 = Symfony 2.8+ profiler + 'profiler_markup_version' => 3, // 1 = original profiler, 2 = Symfony 2.8+ profiler, 3 = Symfony 6.2+ profiler ]); } @@ -157,7 +157,7 @@ public function toolbarAction(Request $request, string $token = null): Response 'templates' => $this->getTemplateManager()->getNames($profile), 'profiler_url' => $url, 'token' => $token, - 'profiler_markup_version' => 2, // 1 = original toolbar, 2 = Symfony 2.8+ toolbar + 'profiler_markup_version' => 3, // 1 = original toolbar, 2 = Symfony 2.8+ profiler, 3 = Symfony 6.2+ profiler ]); } @@ -205,6 +205,7 @@ public function searchBarAction(Request $request): Response 'end' => $end, 'limit' => $limit, 'request' => $request, + 'render_hidden_by_default' => false, ]), 200, ['Content-Type' => 'text/html'] @@ -360,7 +361,7 @@ public function openAction(Request $request): Response } return $this->renderWithCspNonces($request, '@WebProfiler/Profiler/open.html.twig', [ - 'filename' => $filename, + 'file_info' => new \SplFileInfo($filename), 'file' => $file, 'line' => $line, ]); diff --git a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php index d460cf1462035..23a69bc76490c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php +++ b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php @@ -57,9 +57,6 @@ public function load(array $configs, ContainerBuilder $container) ->replaceArgument(3, new ServiceClosureArgument(new Reference('debug.file_link_formatter.url_format'))); } - /** - * {@inheritdoc} - */ public function getXsdValidationBasePath(): string|false { return __DIR__.'/../Resources/config/schema'; diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index 5152223669c8d..efaa98e0ca50f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -85,7 +85,7 @@ public function onKernelResponse(ResponseEvent $event) $this->urlGenerator->generate('_profiler', ['token' => $response->headers->get('X-Debug-Token')], UrlGeneratorInterface::ABSOLUTE_URL) ); } catch (\Exception $e) { - $response->headers->set('X-Debug-Error', \get_class($e).': '.preg_replace('/\s+/', ' ', $e->getMessage())); + $response->headers->set('X-Debug-Error', $e::class.': '.preg_replace('/\s+/', ' ', $e->getMessage())); } } @@ -113,7 +113,7 @@ public function onKernelResponse(ResponseEvent $event) $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } - $response->setContent($this->twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location')])); + $response->setContent($this->twig->render('@WebProfiler/Profiler/toolbar_redirect.html.twig', ['location' => $response->headers->get('Location'), 'host' => $request->getSchemeAndHttpHost()])); $response->setStatusCode(200); $response->headers->remove('Location'); } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/cache.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/cache.html.twig index b2f06be8c5f3b..3f307c8b81b49 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/cache.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/cache.html.twig @@ -47,81 +47,36 @@

Cache

{% if collector.totals.calls == 0 %} -
+

No cache calls were made.

{% else %} -
-
- {{ collector.totals.calls }} - Total calls -
-
- {{ '%0.2f'|format(collector.totals.time * 1000) }} ms - Total time -
-
-
- {{ collector.totals.reads }} - Total reads -
-
- {{ collector.totals.writes }} - Total writes -
-
- {{ collector.totals.deletes }} - Total deletes -
-
-
- {{ collector.totals.hits }} - Total hits -
-
- {{ collector.totals.misses }} - Total misses -
-
- - {{ collector.totals.hit_read_ratio ?? 0 }} % - - Hits/reads -
-
+ {{ _self.render_metrics(collector.totals, true) }}

Pools

- {% for name, calls in collector.calls %} + {# the empty merge is needed to turn the iterator into an array #} + {% set cache_pools_with_calls = collector.calls|filter(calls => calls|length > 0)|merge([]) %} + {% for name, calls in cache_pools_with_calls %}

{{ name }} {{ collector.statistics[name].calls }}

+

Adapter

+
+ {% if collector.adapters[name] is defined %} + {{ collector.adapters[name] }} + {% else %} + Unable to get the adapter class. + {% endif %} +
{% if calls|length == 0 %}

No calls were made for {{ name }} pool.

{% else %}

Metrics

-
- {% for key, value in collector.statistics[name] %} -
- - {% if key == 'time' %} - {{ '%0.2f'|format(1000 * value) }} ms - {% elseif key == 'hit_read_ratio' %} - {{ value ?? 0 }} % - {% else %} - {{ value }} - {% endif %} - - {{ key == 'hit_read_ratio' ? 'Hits/reads' : key|capitalize }} -
- {% if key == 'time' or key == 'deletes' %} -
- {% endif %} - {% endfor %} -
+ {{ _self.render_metrics(collector.statistics[name]) }}

Calls

@@ -147,7 +102,77 @@ {% endif %} + + {% if loop.last %} +
+

Pools without calls {{ collector.calls|filter(calls => 0 == calls|length)|length }}

+ +
+
+ + + + + + + {% for cache_pool in collector.calls|filter(calls => 0 == calls|length)|keys|sort %} + + {% endfor %} + +
Cache pools that received no calls
{{ cache_pool }}
+
+
+ {% endif %} {% endfor %}
{% endif %} {% endblock %} + +{% macro render_metrics(pool, is_total = false) %} +
+
+ {{ pool.calls }} + {{ is_total ? 'Total calls' : 'Calls' }} +
+
+ {{ '%0.2f'|format(pool.time * 1000) }} ms + {{ is_total ? 'Total time' : 'Time' }} +
+ +
+ +
+
+ {{ pool.reads }} + {{ is_total ? 'Total reads' : 'Reads' }} +
+
+ {{ pool.writes }} + {{ is_total ? 'Total writes' : 'Writes' }} +
+
+ {{ pool.deletes }} + {{ is_total ? 'Total deletes' : 'Deletes' }} +
+
+ +
+ +
+
+ {{ pool.hits }} + {{ is_total ? 'Total hits' : 'Hits' }} +
+
+ {{ pool.misses }} + {{ is_total ? 'Total misses' : 'Misses' }} +
+
+ + {{ pool.hit_read_ratio ?? 0 }} % + + Hits/reads +
+
+
+{% endmacro %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig index 5f8cf89b2225e..b0353b87db310 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig @@ -118,7 +118,13 @@
- {{ collector.symfonyversion }} + + {{ collector.symfonyversion }} + + {% if collector.symfonylts %} + (LTS) + {% endif %} + Symfony version
@@ -137,33 +143,39 @@ {% endif %}
- {% set symfony_status = { dev: 'Unstable Version', stable: 'Stable Version', eom: 'Maintenance Ended', eol: 'Version Expired' } %} + {% set symfony_status = { dev: 'In Development', stable: 'Maintained', eom: 'Security Fixes Only', eol: 'Unmaintained' } %} {% set symfony_status_class = { dev: 'warning', stable: 'success', eom: 'warning', eol: 'error' } %} - - - - - - - - - - - - - - - - - -
Symfony StatusBugs {{ collector.symfonystate in ['eom', 'eol'] ? 'were' : 'are' }} fixed untilSecurity issues {{ collector.symfonystate == 'eol' ? 'were' : 'are' }} fixed until
- {{ symfony_status[collector.symfonystate]|upper }} - {% if collector.symfonylts %} -   Long-Term Support - {% endif %} - {{ collector.symfonyeom }}{{ collector.symfonyeol }} - View roadmap -
+ +
+
+
+ + {{ symfony_status[collector.symfonystate]|upper }} + + Your Symfony version status +
+ + {% if collector.symfonylts %} +
+ + {{ collector.symfonyeom }} + + Bug fixes {{ collector.symfonystate in ['eom', 'eol'] ? 'ended on' : 'until' }} +
+ {% endif %} + +
+ + {{ collector.symfonyeol }} + + + {{ collector.symfonylts ? 'Security fixes' : 'Bug fixes and security fixes' }} + {{ 'eol' == collector.symfonystate ? 'ended on' : 'until' }} +
+
+
+ + View Symfony {{ collector.symfonyversion }} release details

PHP Configuration

@@ -190,19 +202,21 @@
-
- {{ source('@WebProfiler/Icon/' ~ (collector.haszendopcache ? 'yes' : 'no') ~ '.svg') }} - OPcache -
+
+
+ {{ source('@WebProfiler/Icon/' ~ (collector.haszendopcache ? 'yes' : 'no') ~ '.svg') }} + OPcache +
-
- {{ source('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no-gray') ~ '.svg') }} - APCu -
+
+ {{ source('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no') ~ '.svg') }} + APCu +
-
- {{ source('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no-gray') ~ '.svg') }} - Xdebug +
+ {{ source('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no') ~ '.svg') }} + Xdebug +
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig index af4f8b5e73ef0..33f33b235963a 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig @@ -13,7 +13,7 @@

Event Dispatcher

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

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

{% else %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig index e27200d7abdf7..baea464527975 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig @@ -26,7 +26,7 @@

Exceptions

{% if not collector.hasexception %} -
+

No exception was thrown and caught during the request.

{% else %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig index bde82228fa42d..2f2f21a9f45c1 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -46,7 +46,7 @@ #tree-menu { float: left; padding-right: 10px; - width: 230px; + width: 220px; } #tree-menu ul { list-style: none; @@ -60,11 +60,12 @@ } #tree-menu .empty { border: 0; + box-shadow: none !important; padding: 0; } #tree-details-container { - border-left: 1px solid #DDD; - margin-left: 250px; + border-left: 1px solid var(--table-border-color); + margin-left: 230px; padding-left: 20px; } .tree-details { @@ -77,13 +78,14 @@ .toggle-icon { display: inline-block; - background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgBAMAAADpp+X/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QweDgwx4LcKwAAAABVQTFRFAAAA////////////////ZmZm////bvjBwAAAAAV0Uk5TABZwsuCVEUjgAAAAAWJLR0QF+G/pxwAAAE1JREFUGNNjSHMSYGBgUEljSGYAAzMGBwiDhUEBwmBiEIAwGBmwgTQgQGWgA7h2uIFwK+CWwp1BpHvYEqDuATEYkBlY3IOmBq6dCPcAAIT5Eg2IksjQAAAAAElFTkSuQmCC") no-repeat top left #5eb5e0; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' data-icon-name='icon-tabler-square-plus' width='24' height='24' viewBox='0 0 24 24' stroke-width='2px' stroke='currentColor' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'%3E%3C/path%3E%3Crect x='4' y='4' width='16' height='16' rx='2'%3E%3C/rect%3E%3Cline x1='9' y1='12' x2='15' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='9' x2='12' y2='15'%3E%3C/line%3E%3C/svg%3E") no-repeat; + background-size: 18px 18px; } .closed .toggle-icon, .closed.toggle-icon { background-position: bottom left; } .toggle-icon.empty { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAZgBmAGYHukptAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QweDhIf6CA40AAAAFRJREFUOMvtk7ENACEMA61vfx767MROWfO+AdGBHlNyTZrYUZRYDBII4NWE1pNdpFarfgLUbpDaBEgBYRiEVjsvDLa1l6O4Z3wkFWN+OfLKdpisOH/TlICzukmUJwAAAABJRU5ErkJggg=="); + background-image: none; } .tree .tree-inner { @@ -94,58 +96,45 @@ text-overflow: ellipsis; } .tree .toggle-button { - /* provide a bigger clickable area than just 10x10px */ width: 16px; height: 16px; margin-left: -18px; } .tree .toggle-icon { - width: 10px; - height: 10px; - /* position the icon in the center of the clickable area */ - margin-left: 3px; - margin-top: 3px; - background-size: 10px 20px; - background-color: #AAA; + width: 18px; + height: 18px; + vertical-align: bottom; } .tree .toggle-icon.empty { - width: 10px; - height: 10px; + width: 5px; + height: 5px; position: absolute; top: 50%; - margin-top: -5px; - margin-left: -15px; - background-size: 10px 10px; + margin-top: -2px; + margin-left: -13px; + } + .tree .tree-inner { + border-radius: 4px; } .tree ul ul .tree-inner { - padding-left: 37px; + padding-left: 32px; } .tree ul ul ul .tree-inner { - padding-left: 52px; + padding-left: 48px; } .tree ul ul ul ul .tree-inner { - padding-left: 67px; + padding-left: 64px; } .tree ul ul ul ul ul .tree-inner { - padding-left: 82px; + padding-left: 72px; } .tree .tree-inner:hover { - background: #dfdfdf; - } - .tree .tree-inner:hover span:not(.has-error) { - color: var(--base-0); + background: var(--gray-200); } .tree .tree-inner.active, .tree .tree-inner.active:hover { background: var(--tree-active-background); font-weight: bold; } - .tree .tree-inner.active .toggle-icon, .tree .tree-inner:hover .toggle-icon, .tree .tree-inner.active:hover .toggle-icon { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgBAMAAADpp+X/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QweDhEYXWn+sAAAABhQTFRFAAAA39/f39/f39/f39/fZmZm39/f////gc3YPwAAAAV0Uk5TAAtAc6ZeVyCYAAAAAWJLR0QF+G/pxwAAAE1JREFUGNNjSHMSYGBgUEljSGYAAzMGBwiDhUEBwmBiEIAwGBmwgXIgQGWgA7h2uIFwK+CWwp1BpHvYC6DuATEYkBlY3IOmBq6dCPcAADqLE4MnBi/fAAAAAElFTkSuQmCC"); - background-color: #999; - } - .tree .tree-inner.active .toggle-icon.empty, .tree .tree-inner:hover .toggle-icon.empty, .tree .tree-inner.active:hover .toggle-icon.empty { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QweDhoucSey4gAAABVQTFRFAAAA39/f39/f39/f39/fZmZm39/fD5Dx2AAAAAV0Uk5TAAtAc6ZeVyCYAAAAAWJLR0QF+G/pxwAAADJJREFUCNdjSHMSYGBgUEljSGYAAzMGBwiDhUEBwmBiEIAwGBnIA3DtcAPhVsAthTkDAFOfBKW9C1iqAAAAAElFTkSuQmCC"); - } .tree-details .toggle-icon { width: 16px; height: 16px; @@ -181,26 +170,6 @@ color: inherit; text-decoration: inherit; } - h2 + h3.form-data-type { - margin-top: 0; - } - h3.form-data-type + h3 { - margin-top: 1em; - } - .theme-dark .toggle-icon { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgBAMAAADpp+X/AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAVUExURUdwTH+Ag0lNUZiYmGRmbP///zU5P2n9VV4AAAAFdFJOUwCv+yror0g1sQAAAE1JREFUGNNjSFM0YGBgEEpjSGEAAzcGBQiDiUEAwmBkMIAwmBmwgVAgQGWgA7h2uIFwK+CWwp1BpHtYA6DuATEYkBlY3IOmBq6dCPcAAKMtEEs3tfChAAAAAElFTkSuQmCC'); - } - .theme-dark .toggle-icon.empty { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAASUExURUdwTDI3OzQ5PS4uLjU3PzU5P4keoyIAAAAFdFJOUwBApgtzrnKGEwAAADJJREFUCNdjCFU0YGBgEAplCGEAA1cGBQiDiUEAwmBkMIAwmBnIA3DtcAPhVsAthTkDACsZBBmrTTSxAAAAAElFTkSuQmCC'); - } - .theme-dark .tree .tree-inner.active .toggle-icon, .theme-dark .tree .tree-inner:hover .toggle-icon, .theme-dark .tree .tree-inner.active:hover .toggle-icon { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgBAMAAADpp+X/AAAAD1BMVEVHcEx/gIOYmJiZmZn///+IJ2wIAAAAA3RSTlMAryoIUq0uAAAAUElEQVQY02NgYFQ2NjYWYGBgMAYDBgZmCMOAQRjCMGRQhjCMoEqAipAYLkCAykBXA9cONxBuBdxShDOIc4+JM9Q9IIYxMgOLe9DUwLUT4R4AznguG0qfEa0AAAAASUVORK5CYII='); - background-color: transparent; - } - .theme-dark .tree .tree-inner.active .toggle-icon.empty, .theme-dark .tree .tree-inner:hover .toggle-icon.empty, .theme-dark .tree .tree-inner.active:hover .toggle-icon.empty { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEVHcEwyNzuqqqrd9nIgAAAAAnRSTlMAQABPjKgAAAArSURBVAjXY2BctcqBgWvVqgUMWqtWrWDIWrVqJcMqICCGACsGawMbADIKANflJYEoGMqtAAAAAElFTkSuQmCC'); - background-color: transparent; - } {% endblock %} @@ -222,7 +191,7 @@ {% endfor %}
{% else %} -
+

No forms were submitted for this request.

{% endif %} @@ -442,7 +411,6 @@ }; tabTarget.initTabs(document.querySelectorAll('.tree .tree-inner')); - toggler.initButtons(document.querySelectorAll('a.toggle-button')); {% endblock %} @@ -481,10 +449,71 @@

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

{% if data.type_class is defined %} -

{{ profiler_dump(data.type_class) }}

+
+ Form type: + {{ profiler_dump(data.type_class) }} +
{% endif %} - {% if data.errors is defined and data.errors|length > 0 %} + {% set form_has_errors = data.errors ?? [] is not empty %} +
+
+

Errors

+ +
+ {{ _self.render_form_errors(data) }} +
+
+ +
+

Default Data

+ +
+ {{ _self.render_form_default_data(data) }} +
+
+ +
+

Submitted Data

+ +
+ {{ _self.render_form_submitted_data(data) }} +
+
+ +
+

Passed Options

+ +
+ {{ _self.render_form_passed_options(data) }} +
+
+ +
+

Resolved Options

+ +
+ {{ _self.render_form_resolved_options(data) }} +
+
+ +
+

View Vars

+ +
+ {{ _self.render_form_view_variables(data) }} +
+
+
+
+ + {% for childName, childData in data.children %} + {{ tree.form_tree_details(childName, childData, forms_by_hash) }} + {% endfor %} +{% endmacro %} + +{% macro render_form_errors(data) %} + {% if data.errors is defined and data.errors|length > 0 %} - {% endif %} - - {% if data.default_data is defined %} -

- - Default Data - -

- -
- - - - - - + {% for error in data.errors %} - + - - - - - - - + {% endfor %}
PropertyValue
Model Format{{ error.message }} - {% if data.default_data.model is defined %} - {{ profiler_dump(data.default_data.seek('model')) }} + {% if error.origin is empty %} + This form. + {% elseif forms_by_hash[error.origin] is not defined %} + Unknown. {% else %} - same as normalized format + {{ forms_by_hash[error.origin].name }} {% endif %}
Normalized Format{{ profiler_dump(data.default_data.seek('norm')) }}
View Format - {% if data.default_data.view is defined %} - {{ profiler_dump(data.default_data.seek('view')) }} + {% if error.trace %} + Caused by: + {% for stacked in error.trace %} + {{ profiler_dump(stacked) }} + {% endfor %} {% else %} - same as normalized format + Unknown. {% endif %}
- {% endif %} - - {% if data.submitted_data is defined %} -

- - Submitted Data - -

+ {% else %} +
+

This form has no errors.

+
+ {% endif %} +{% endmacro %} -
- {% if data.submitted_data.norm is defined %} - - - - - - - - - - - - - - - - - - - - - -
PropertyValue
View Format - {% if data.submitted_data.view is defined %} - {{ profiler_dump(data.submitted_data.seek('view')) }} - {% else %} - same as normalized format - {% endif %} -
Normalized Format{{ profiler_dump(data.submitted_data.seek('norm')) }}
Model Format - {% if data.submitted_data.model is defined %} - {{ profiler_dump(data.submitted_data.seek('model')) }} - {% else %} - same as normalized format - {% endif %} -
- {% else %} -
-

This form was not submitted.

-
- {% endif %} +{% macro render_form_default_data(data) %} + {% if data.default_data is defined %} + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Model Format + {% if data.default_data.model is defined %} + {{ profiler_dump(data.default_data.seek('model')) }} + {% else %} + same as normalized format + {% endif %} +
Normalized Format{{ profiler_dump(data.default_data.seek('norm')) }}
View Format + {% if data.default_data.view is defined %} + {{ profiler_dump(data.default_data.seek('view')) }} + {% else %} + same as normalized format + {% endif %} +
+ {% else %} +
+

This form has default data defined.

- {% endif %} + {% endif %} +{% endmacro %} - {% if data.passed_options is defined %} -

- - Passed Options - -

+{% macro render_form_submitted_data(data) %} + {% if data.submitted_data.norm is defined %} + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
View Format + {% if data.submitted_data.view is defined %} + {{ profiler_dump(data.submitted_data.seek('view')) }} + {% else %} + same as normalized format + {% endif %} +
Normalized Format{{ profiler_dump(data.submitted_data.seek('norm')) }}
Model Format + {% if data.submitted_data.model is defined %} + {{ profiler_dump(data.submitted_data.seek('model')) }} + {% else %} + same as normalized format + {% endif %} +
+ {% else %} +
+

This form was not submitted.

+
+ {% endif %} +{% endmacro %} -
- {% if data.passed_options|length %} - - - - - - - - - - {% for option, value in data.passed_options %} +{% macro render_form_passed_options(data) %} + {% if data.passed_options ?? [] is not empty %} +
OptionPassed ValueResolved Value
+ + + + + + + + + {% for option, value in data.passed_options %} @@ -659,73 +676,50 @@ {% endif %} - {% endfor %} - -
OptionPassed ValueResolved Value
{{ option }} {{ profiler_dump(value) }}
- {% else %} -
-

No options were passed when constructing this form.

-
- {% endif %} -
- {% endif %} - - {% if data.resolved_options is defined %} -

- - Resolved Options - -

- - +{% macro render_form_resolved_options(data) %} + + + + + + + + + {% for option, value in data.resolved_options ?? [] %} + + + + + {% endfor %} + +
OptionValue
{{ option }}{{ profiler_dump(value) }}
+{% endmacro %} - {% for childName, childData in data.children %} - {{ tree.form_tree_details(childName, childData, forms_by_hash) }} - {% endfor %} +{% macro render_form_view_variables(data) %} + + + + + + + + + {% for variable, value in data.view_vars %} + + + + + {% endfor %} + +
VariableValue
{{ variable }}{{ profiler_dump(value) }}
{% endmacro %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/http_client.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/http_client.html.twig index f409a2dc03fde..ed926bf8c2fcc 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/http_client.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/http_client.html.twig @@ -38,7 +38,7 @@ {% block panel %}

HTTP Client

{% if collector.requestCount == 0 %} -
+

No HTTP requests were made.

{% else %} @@ -77,17 +77,14 @@ {% endif %} {% endfor %} {% endif %} - +
{% if profiler_token and profiler_link %} {% endif %} + {% if trace.options is not empty %} + + + + + {% endif %} - + {% if trace.http_code >= 500 %} {% set responseStatus = 'error' %} {% elseif trace.http_code >= 400 %} @@ -111,11 +115,10 @@ {% else %} {% set responseStatus = 'success' %} {% endif %} - + {{ trace.http_code }} - - + {{ profiler_dump(trace.info, maxDepth=1) }} {% if profiler_token and profiler_link %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index 5246dc1049f0f..cf82587dd284f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -47,7 +47,7 @@

Log Messages

{% if collector.processedLogs is empty %} -
+

No log messages available.

{% else %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig index 1b898ad5397ec..651c2a1626198 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig @@ -24,43 +24,6 @@ {% endif %} {% endblock %} -{% block head %} - {{ parent() }} - -{% endblock %} - {% block menu %} {% set events = collector.events %} @@ -78,156 +41,224 @@ {% block panel %} {% set events = collector.events %} -

Emails

{% if not events.messages|length %} -
+

No emails were sent.

- {% endif %} + {% else %} +
+
+
+ {{ events.events|filter(e => e.isQueued())|length }} + Queued +
-
-
- {{ events.events|filter(e => e.isQueued())|length }} - Queued +
+ {{ events.events|filter(e => not e.isQueued())|length }} + Sent +
+
+ {% endif %} -
- {{ events.events|filter(e => not e.isQueued())|length }} - Sent + {% if events.transports|length > 1 %} + {% for transport in events.transports %} +

{{ transport }} transport

+ {{ _self.render_transport_details(collector, transport) }} + {% endfor %} + {% elseif events.transports is not empty %} + {{ _self.render_transport_details(collector, events.transports|first, true) }} + {% endif %} + + {% macro render_transport_details(collector, transport, show_transport_name = false) %} +
+ {% set num_emails = collector.events.events(transport)|length %} + {% if num_emails > 1 %} +
+
- {{ trace.method }} + {{ trace.method }} {{ trace.url }} - {% if trace.options is not empty %} - {{ profiler_dump(trace.options, maxDepth=1) }} - {% endif %} @@ -96,14 +93,21 @@ {% endif %} {% if trace.curlCommand is not null %} - +
Request options{{ profiler_dump(trace.options, maxDepth=1) }}
+ Response
+ + + + + + + + + + {% for event in collector.events.events(transport) %} + + + + + + + {% endfor %} + +
#SubjectToActions
{{ loop.index }}{{ event.message.headers.get('subject').bodyAsString() ?? '(No subject)' }}{{ (event.message.headers.get('to').bodyAsString() ?? '(empty)')|replace({'To:': ''}) }}
+
+ + {% for event in collector.events.events(transport) %} +
+ {{ _self.render_email_details(collector, transport, event.message, event.isQueued, show_transport_name) }} +
+ {% endfor %} + + + {% else %} + {% set event = (collector.events.events(transport)|first) %} + {{ _self.render_email_details(collector, transport, event.message, event.isQueued, show_transport_name) }} + {% endif %}
-
- - {% for transport in events.transports %} -
-
- {% for event in events.events(transport) %} - {% set message = event.message %} -
-

Email {{ event.isQueued() ? 'queued' : 'sent via ' ~ transport }}

-
-
- {% if message.headers is not defined %} - {# RawMessage instance #} -
-
{{ message.toString() }}
-
- {% else %} - {# Message instance #} -
-
-
-

Headers

-
- Subject -

{{ message.headers.get('subject').bodyAsString() ?? '(empty)' }}

-
-
- From -
{{ (message.headers.get('from').bodyAsString() ?? '(empty)')|replace({'From:': ''}) }}
- - To -
{{ (message.headers.get('to').bodyAsString() ?? '(empty)')|replace({'To:': ''}) }}
- - {% if event.envelope.recipients|length > 0 %} - Recipients - {% for recipient in event.envelope.recipients %} -
{{ recipient.address }}
- {% endfor %} - {% endif %} -
-
- Headers -
{% for header in message.headers.all|filter(header => (header.name ?? '') not in ['Subject', 'From', 'To']) %}
-                                                                {{- header.toString }}
-                                                            {%~ endfor %}
-
-
-
-
- {% if message.htmlBody is defined %} - {# Email instance #} - {% set htmlBody = message.htmlBody() %} - {% if htmlBody is not null %} -
-

HTML Preview

-
-
-                                                                
-                                                            
-
-
-
-

HTML Content

-
-
-                                                                {%- if message.htmlCharset() %}
-                                                                    {{- htmlBody|convert_encoding('UTF-8', message.htmlCharset()) }}
-                                                                {%- else %}
-                                                                    {{- htmlBody }}
-                                                                {%- endif -%}
-                                                            
-
-
- {% endif %} - {% set textBody = message.textBody() %} - {% if textBody is not null %} -
-

Text Content

-
-
-                                                                {%- if message.textCharset() %}
-                                                                    {{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
-                                                                {%- else %}
-                                                                    {{- textBody }}
-                                                                {%- endif -%}
-                                                            
-
-
- {% endif %} - {% for attachment in message.attachments %} - - {% endfor %} + {% endmacro %} + + {% macro render_email_details(collector, transport, message, message_is_queued, show_transport_name = false) %} + {% if show_transport_name %} +

+ Status: {{ message_is_queued ? 'Queued' : 'Sent' }} + • + Transport: {{ transport }} +

+ {% endif %} + + {% if message.headers is not defined %} + {# render the raw message contents #} + + {{ source('@WebProfiler/Icon/download.svg') }} + Download as EML file + + +
{{ message.toString() }}
+ {% else %} +
+
+

Email contents

+
+
+

+ {{ message.headers.get('subject').bodyAsString() ?? '(No subject)' }} +

+
+

From: {{ (message.headers.get('from').bodyAsString() ?? '(empty)')|replace({'From:': ''}) }}

+

To: {{ (message.headers.get('to').bodyAsString() ?? '(empty)')|replace({'To:': ''}) }}

+ {% for header in message.headers.all|filter(header => (header.name ?? '') not in ['Subject', 'From', 'To']) %} +

{{ header.toString }}

+ {% endfor %} +
+
+ + {% if message.attachments %} +
+ {% set num_of_attachments = message.attachments|length %} + {% set total_attachments_size_in_bytes = message.attachments|reduce((total_size, attachment) => total_size + attachment.body|length) %} +

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

+ +
    + {% for attachment in message.attachments %} +
  • + {{ source('@WebProfiler/Icon/file.svg') }} + + {% if attachment.filename|default %} + {{ attachment.filename }} + {% else %} + (no filename) {% endif %} -
    -

    Parts Hierarchy

    -
    -
    {{ message.body().asDebugString() }}
    -
    + + ({{ _self.render_file_size_humanized(attachment.body|length) }}) + + Download +
  • + {% endfor %} +
+
+ {% endif %} + + {% if message.htmlBody or message.textBody %} +
+
+ {% if message.htmlBody %} + {% set htmlBody = message.htmlBody() %} +
+

HTML content

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

Raw

-
-
{{ message.toString() }}
-
+
+ +
+

HTML preview

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

Text content

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

MIME parts

+
+
{{ message.body().asDebugString() }}
+
+
+ +
+

Raw Message

+ +
-
- {% endfor %} + {% endif %} + {% endmacro %} + + {% macro render_file_size_humanized(bytes) %} + {%- if bytes < 1000 -%} + {{- bytes ~ ' bytes' -}} + {%- elseif bytes < 1000 ** 2 -%} + {{- (bytes / 1000)|number_format(2) ~ ' kB' -}} + {%- else -%} + {{- (bytes / 1000 ** 2)|number_format(2) ~ ' MB' -}} + {%- endif -%} + {% endmacro %} {% endblock %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig index 0d0f11457f408..1dc5accf1c3a4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig @@ -57,8 +57,6 @@ .message-bus .badge.status-some-errors { line-height: 16px; border-bottom: 2px solid #B0413E; } .message-item tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; } - td.message-bus-dispatch-caller { background: #f1f2f3; } - .theme-dark td.message-bus-dispatch-caller { background: var(--base-1); } {% endblock %} @@ -68,9 +66,12 @@

Messages

{% if collector.messages is empty %} -
+

No messages have been collected.

+ {% elseif 1 == collector.buses|length %} +

Ordered list of dispatched messages across all your buses

+ {{ helper.render_bus_messages(collector.messages, true) }} {% else %}
@@ -112,9 +113,6 @@ data-toggle-initial="{{ loop.first ? 'display' }}" > {{ profiler_dump(dispatchCall.message.type) }} - {% if showBus %} - {{ dispatchCall.bus }} - {% endif %} {% if dispatchCall.exception is defined %} exception {% endif %} @@ -127,21 +125,21 @@ - - + {% else %} + {{ caller.name }} + {% endif %} + line {{ caller.line }}