diff --git a/.appveyor.yml b/.appveyor.yml index 5b64f24d01a04..67dcba9b569f6 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,13 +13,13 @@ init: install: - mkdir c:\php && cd c:\php - - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php-8.0.2-Win32-vs16-x86.zip - - 7z x php-8.0.2-Win32-vs16-x86.zip -y >nul + - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php-8.1.0-Win32-vs16-x86.zip + - 7z x php-8.1.0-Win32-vs16-x86.zip -y >nul - cd ext - - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_apcu-5.1.21-8.0-ts-vs16-x86.zip - - 7z x php_apcu-5.1.21-8.0-ts-vs16-x86.zip -y >nul - - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_redis-5.3.5-8.0-ts-vs16-x86.zip - - 7z x php_redis-5.3.5-8.0-ts-vs16-x86.zip -y >nul + - 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 - cd .. - copy /Y php.ini-development php.ini-min - echo memory_limit=-1 >> php.ini-min diff --git a/.github/build-packages.php b/.github/build-packages.php index 30dcf0c9adce8..d69a3c8198ec0 100644 --- a/.github/build-packages.php +++ b/.github/build-packages.php @@ -35,7 +35,7 @@ 'type' => 'composer', 'url' => 'file://'.str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__)).'/', ]]; - if (false === strpos($json, "\n \"repositories\": [\n")) { + if (!str_contains($json, "\n \"repositories\": [\n")) { $json = rtrim(json_encode(['repositories' => $package->repositories], $flags), "\n}").','.substr($json, 1); file_put_contents($dir.'/composer.json', $json); } diff --git a/.github/expected-missing-return-types.diff b/.github/expected-missing-return-types.diff index d45a91c61c7b8..9c69e3869d510 100644 --- a/.github/expected-missing-return-types.diff +++ b/.github/expected-missing-return-types.diff @@ -1,23 +1,24 @@ # Run these steps to update this file: sed -i 's/ *"\*\*\/Tests\/"//' composer.json composer u -o -SYMFONY_PATCH_TYPE_DECLARATIONS='force=2&php=8.0' php .github/patch-types.php +SYMFONY_PATCH_TYPE_DECLARATIONS='force=2&php=8.1' php .github/patch-types.php head=$(sed '/^diff /Q' .github/expected-missing-return-types.diff) -(echo "$head" && echo && git diff -U2 composer.json src/) > .github/expected-missing-return-types.diff - -diff --git a/composer.json b/composer.json -index 978743d34d..1f185f682c 100644 ---- a/composer.json -+++ b/composer.json -@@ -180,5 +180,5 @@ - ], - "exclude-from-classmap": [ -- "**/Tests/" -+ - ] - }, +(echo "$head" && echo && git diff -U2 src/) > .github/expected-missing-return-types.diff +git checkout composer.json src/ + +diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +index d68ae4c8b3..8e980a9e70 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php ++++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +@@ -88,5 +88,5 @@ abstract class KernelTestCase extends TestCase + * @return TestContainer + */ +- protected static function getContainer(): ContainerInterface ++ protected static function getContainer(): TestContainer + { + if (!static::$booted) { diff --git a/src/Symfony/Component/BrowserKit/AbstractBrowser.php b/src/Symfony/Component/BrowserKit/AbstractBrowser.php -index 152050159b..e2ec1aeea2 100644 +index 697e34cb77..9a1e4c5618 100644 --- a/src/Symfony/Component/BrowserKit/AbstractBrowser.php +++ b/src/Symfony/Component/BrowserKit/AbstractBrowser.php @@ -408,5 +408,5 @@ abstract class AbstractBrowser @@ -32,7 +33,7 @@ index 152050159b..e2ec1aeea2 100644 */ - abstract protected function doRequest(object $request); + abstract protected function doRequest(object $request): object; - + /** @@ -460,5 +460,5 @@ abstract class AbstractBrowser * @return object @@ -48,6 +49,17 @@ index 152050159b..e2ec1aeea2 100644 + protected function filterResponse(object $response): Response { 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 +--- 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 + * @return NodeDefinition&ParentNodeDefinitionInterface + */ +- public function end() ++ public function end(): NodeDefinition&ParentNodeDefinitionInterface + { + return $this->parent; diff --git a/src/Symfony/Component/Config/Definition/ConfigurationInterface.php b/src/Symfony/Component/Config/Definition/ConfigurationInterface.php index 7b5d443fe6..d64ae0d024 100644 --- a/src/Symfony/Component/Config/Definition/ConfigurationInterface.php @@ -110,21 +122,21 @@ index b94a4378f5..db502e12a7 100644 */ - public function load(mixed $resource, string $type = null); + public function load(mixed $resource, string $type = null): mixed; - + /** @@ -35,5 +35,5 @@ interface LoaderInterface * @return bool */ - public function supports(mixed $resource, string $type = null); + public function supports(mixed $resource, string $type = null): bool; - + /** @@ -42,5 +42,5 @@ interface LoaderInterface * @return LoaderResolverInterface */ - public function getResolver(); + public function getResolver(): LoaderResolverInterface; - + /** diff --git a/src/Symfony/Component/Config/ResourceCheckerInterface.php b/src/Symfony/Component/Config/ResourceCheckerInterface.php index 6b1c6c5fbe..bb80ed461e 100644 @@ -135,7 +147,7 @@ index 6b1c6c5fbe..bb80ed461e 100644 */ - public function supports(ResourceInterface $metadata); + public function supports(ResourceInterface $metadata): bool; - + /** @@ -42,4 +42,4 @@ interface ResourceCheckerInterface * @return bool @@ -144,7 +156,7 @@ 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 09234f5eb5..24d995ad6d 100644 +index c654b93790..ce744f5c6d 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -218,5 +218,5 @@ class Application implements ResetInterface @@ -197,23 +209,47 @@ index 09234f5eb5..24d995ad6d 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 e69bae0982..3390628d0d 100644 +index e84307207a..bc503462c1 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php -@@ -171,5 +171,5 @@ class Command +@@ -187,5 +187,5 @@ class Command * @return bool */ - public function isEnabled() + public function isEnabled(): bool { return true; -@@ -197,5 +197,5 @@ class Command +@@ -213,5 +213,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.'); +diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatter.php b/src/Symfony/Component/Console/Formatter/OutputFormatter.php +index 5f75bf1..75f95fb 100644 +--- a/src/Symfony/Component/Console/Formatter/OutputFormatter.php ++++ b/src/Symfony/Component/Console/Formatter/OutputFormatter.php +@@ -136,7 +136,7 @@ class OutputFormatter implements WrappableOutputFormatterInterface + /** + * {@inheritdoc} + */ +- public function formatAndWrap(?string $message, int $width) ++ public function formatAndWrap(?string $message, int $width): string + { + $offset = 0; + $output = ''; +diff --git a/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php b/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php +index 746cd27..52c6142 100644 +--- a/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php ++++ b/src/Symfony/Component/Console/Formatter/WrappableOutputFormatterInterface.php +@@ -23,5 +23,5 @@ interface WrappableOutputFormatterInterface extends OutputFormatterInterface + * + * @return string + */ +- public function formatAndWrap(?string $message, int $width); ++ public function formatAndWrap(?string $message, int $width): string; + } diff --git a/src/Symfony/Component/Console/Helper/HelperInterface.php b/src/Symfony/Component/Console/Helper/HelperInterface.php index 1d2b7bfb84..cb1f66152d 100644 --- a/src/Symfony/Component/Console/Helper/HelperInterface.php @@ -225,32 +261,32 @@ index 1d2b7bfb84..cb1f66152d 100644 + public function getName(): string; } diff --git a/src/Symfony/Component/Console/Input/InputInterface.php b/src/Symfony/Component/Console/Input/InputInterface.php -index 024da1884e..943790e875 100644 +index 3af991a76f..742e2508f3 100644 --- a/src/Symfony/Component/Console/Input/InputInterface.php +++ b/src/Symfony/Component/Console/Input/InputInterface.php -@@ -54,5 +54,5 @@ interface InputInterface +@@ -57,5 +57,5 @@ interface InputInterface * @return mixed */ - public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false); + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed; - + /** -@@ -84,5 +84,5 @@ interface InputInterface +@@ -87,5 +87,5 @@ interface InputInterface * @throws InvalidArgumentException When argument given doesn't exist */ - public function getArgument(string $name); + public function getArgument(string $name): mixed; - + /** -@@ -112,5 +112,5 @@ interface InputInterface +@@ -115,5 +115,5 @@ interface InputInterface * @throws InvalidArgumentException When option given doesn't exist */ - public function getOption(string $name); + public function getOption(string $name): mixed; - + /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php -index e9fa5a6808..016e9d893a 100644 +index c2824f4578..032f5c2318 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 @@ -261,7 +297,7 @@ index e9fa5a6808..016e9d893a 100644 { if (\is_array($value)) { diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php -index 0532120adf..78fba5ef79 100644 +index f9820f5ede..d28a418429 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -108,5 +108,5 @@ class Container implements ContainerInterface, ResetInterface @@ -272,7 +308,7 @@ index 0532120adf..78fba5ef79 100644 { return $this->parameterBag->get($name); diff --git a/src/Symfony/Component/DependencyInjection/ContainerInterface.php b/src/Symfony/Component/DependencyInjection/ContainerInterface.php -index aa5d6b317e..31ffbca4ef 100644 +index cad44026c0..14cd192e07 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerInterface.php +++ b/src/Symfony/Component/DependencyInjection/ContainerInterface.php @@ -53,5 +53,5 @@ interface ContainerInterface extends PsrContainerInterface @@ -280,7 +316,7 @@ index aa5d6b317e..31ffbca4ef 100644 */ - public function getParameter(string $name); + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null; - + public function hasParameter(string $name): bool; diff --git a/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php b/src/Symfony/Component/DependencyInjection/Extension/ConfigurationExtensionInterface.php index a42967f4da..4e86e16f9d 100644 @@ -326,14 +362,14 @@ index f2373ed5ea..1eec21a938 100644 */ - public function getNamespace(); + public function getNamespace(): string; - + /** @@ -40,5 +40,5 @@ interface ExtensionInterface * @return string|false */ - public function getXsdValidationBasePath(); + public function getXsdValidationBasePath(): string|false; - + /** @@ -49,4 +49,4 @@ interface ExtensionInterface * @return string @@ -372,7 +408,7 @@ 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 5a077a42a6..62a234b116 100644 +index 79d61e8bc0..7f34d95d84 100644 --- a/src/Symfony/Component/Form/AbstractExtension.php +++ b/src/Symfony/Component/Form/AbstractExtension.php @@ -114,5 +114,5 @@ abstract class AbstractExtension implements FormExtensionInterface @@ -398,7 +434,7 @@ index 3dbe0a8420..b4179c785c 100644 */ - abstract protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName); + abstract protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName): bool; - + /** diff --git a/src/Symfony/Component/Form/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php index 3325b8bc27..1cc22a1dab 100644 @@ -427,7 +463,7 @@ index 8495905e17..1e53be60be 100644 */ - public function transform(mixed $value); + public function transform(mixed $value): mixed; - + /** @@ -89,4 +89,4 @@ interface DataTransformerInterface * @throws TransformationFailedException when the transformation fails @@ -454,21 +490,21 @@ index 61e2c5f80d..4d6b335474 100644 */ - public function guessType(string $class, string $property); + public function guessType(string $class, string $property): ?Guess\TypeGuess; - + /** @@ -29,5 +29,5 @@ interface FormTypeGuesserInterface * @return Guess\ValueGuess|null */ - public function guessRequired(string $class, string $property); + public function guessRequired(string $class, string $property): ?Guess\ValueGuess; - + /** @@ -36,5 +36,5 @@ interface FormTypeGuesserInterface * @return Guess\ValueGuess|null */ - public function guessMaxLength(string $class, string $property); + public function guessMaxLength(string $class, string $property): ?Guess\ValueGuess; - + /** @@ -50,4 +50,4 @@ interface FormTypeGuesserInterface * @return Guess\ValueGuess|null @@ -485,7 +521,7 @@ index 2b9066a511..1c9e9f5a26 100644 */ - public function getBlockPrefix(); + public function getBlockPrefix(): string; - + /** @@ -84,4 +84,4 @@ interface FormTypeInterface * @return string|null @@ -514,10 +550,10 @@ index 2f442cb536..d98909cfae 100644 + public function warmUp(string $cacheDir): array; } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php -index 1289cf0ea9..dabeec97ee 100644 +index 3a3be3af49..971560c07b 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php -@@ -58,5 +58,5 @@ abstract class DataCollector implements DataCollectorInterface +@@ -59,5 +59,5 @@ abstract class DataCollector implements DataCollectorInterface * @return callable[] The casters to add to the cloner */ - protected function getCasters() @@ -535,7 +571,7 @@ 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 eeec55593f..c12df944fc 100644 +index cf0e243c6b..292b907ea8 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 @@ -544,9 +580,9 @@ index eeec55593f..c12df944fc 100644 - protected function forward(Request $request, bool $catch = false, Response $entry = null) + protected function forward(Request $request, bool $catch = false, Response $entry = null): Response { - if ($this->surrogate) { + $this->surrogate?->addSurrogateCapability($request); diff --git a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php -index fc70d8970d..9dde6d1f93 100644 +index 1557da575a..7e5a2a9859 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php +++ b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php @@ -61,5 +61,5 @@ class HttpKernelBrowser extends AbstractBrowser @@ -572,17 +608,46 @@ index 19ff0db181..f0f4a5829f 100644 */ - public function getLogs(Request $request = null); + public function getLogs(Request $request = null): array; - + /** @@ -37,5 +37,5 @@ interface DebugLoggerInterface * @return int */ - public function countErrors(Request $request = null); + public function countErrors(Request $request = null): int; - + /** +diff --git a/src/Symfony/Component/Lock/LockFactory.php b/src/Symfony/Component/Lock/LockFactory.php +index 125b6eae50..ac327e8981 100644 +--- a/src/Symfony/Component/Lock/LockFactory.php ++++ b/src/Symfony/Component/Lock/LockFactory.php +@@ -44,5 +44,5 @@ class LockFactory implements LoggerAwareInterface + * @return SharedLockInterface + */ +- public function createLock(string $resource, ?float $ttl = 300.0, bool $autoRelease = true): LockInterface ++ public function createLock(string $resource, ?float $ttl = 300.0, bool $autoRelease = true): SharedLockInterface + { + return $this->createLockFromKey(new Key($resource), $ttl, $autoRelease); +@@ -58,5 +58,5 @@ class LockFactory implements LoggerAwareInterface + * @return SharedLockInterface + */ +- public function createLockFromKey(Key $key, ?float $ttl = 300.0, bool $autoRelease = true): LockInterface ++ public function createLockFromKey(Key $key, ?float $ttl = 300.0, bool $autoRelease = true): SharedLockInterface + { + $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 +--- 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 + * @return MessageCountAwareInterface&ReceiverInterface + */ +- private function getReceiver(): ReceiverInterface ++ private function getReceiver(): MessageCountAwareInterface&ReceiverInterface + { + 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 fe77644762..09dcfe166b 100644 +index 205c15b4cd..e93e460ed1 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -486,5 +486,5 @@ class OptionsResolver implements Options @@ -629,35 +694,35 @@ index fbb37d9f94..522e0487a9 100644 */ - public function getLength(); + public function getLength(): int; - + /** @@ -43,5 +43,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 * @return list */ - public function getElements(); + public function getElements(): array; - + /** @@ -61,5 +61,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 * @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 * @throws Exception\OutOfBoundsException If the offset is invalid @@ -666,10 +731,10 @@ index fbb37d9f94..522e0487a9 100644 + 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 cfffdb2f71..3e2261898a 100644 +index 972d169caa..0b6dae1abe 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php -@@ -725,5 +725,5 @@ class PropertyAccessorTest extends TestCase +@@ -743,5 +743,5 @@ class PropertyAccessorTest extends TestCase * @return mixed */ - public function getFoo() @@ -685,7 +750,7 @@ index f9ee787130..61f8b6d5be 100644 */ - public function isReadable(string $class, string $property, array $context = []); + public function isReadable(string $class, string $property, array $context = []): ?bool; - + /** @@ -31,4 +31,4 @@ interface PropertyAccessExtractorInterface * @return bool|null @@ -725,7 +790,7 @@ index 2dd0e5efbf..95e01d8955 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 be653e4f00..e46300d474 100644 +index 83c10427a1..e113d4a194 100644 --- a/src/Symfony/Component/Routing/Router.php +++ b/src/Symfony/Component/Routing/Router.php @@ -178,5 +178,5 @@ class Router implements RouterInterface, RequestMatcherInterface @@ -754,7 +819,7 @@ index eda4730004..00cfc5b9c7 100644 */ - public function loadTokenBySeries(string $series); + public function loadTokenBySeries(string $series): PersistentTokenInterface; - + /** 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 @@ -767,7 +832,7 @@ index 7e401c3ff3..6b446ff376 100644 + public function vote(TokenInterface $token, mixed $subject, array $attributes): int; } diff --git a/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php b/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php -index 9e2b02b603..c6a753f1f7 100644 +index 298bc78cd9..3ef4176dd8 100644 --- a/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php +++ b/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php @@ -89,5 +89,5 @@ class AuthenticationException extends RuntimeException @@ -786,14 +851,14 @@ index ec90d413fa..9f1401aa91 100644 */ - public function refreshUser(UserInterface $user); + public function refreshUser(UserInterface $user): UserInterface; - + /** @@ -52,5 +52,5 @@ interface UserProviderInterface * @return bool */ - public function supportsClass(string $class); + public function supportsClass(string $class): bool; - + /** diff --git a/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php b/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php index 91271d14a3..100c2fb549 100644 @@ -806,7 +871,7 @@ 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 546b77d22f..24038d4b94 100644 +index 0c313f8f09..acfc9f4b88 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 @@ -827,7 +892,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 84a84ad1f3..6f66b6d32a 100644 +index f38069e471..0966eb3e89 100644 --- a/src/Symfony/Component/Serializer/Encoder/DecoderInterface.php +++ b/src/Symfony/Component/Serializer/Encoder/DecoderInterface.php @@ -35,5 +35,5 @@ interface DecoderInterface @@ -835,13 +900,13 @@ index 84a84ad1f3..6f66b6d32a 100644 */ - public function decode(string $data, string $format, array $context = []); + public function decode(string $data, string $format, array $context = []): mixed; - + /** -@@ -44,4 +44,4 @@ interface DecoderInterface +@@ -45,4 +45,4 @@ interface DecoderInterface * @return bool */ -- public function supportsDecoding(string $format); -+ public function supportsDecoding(string $format): bool; +- public function supportsDecoding(string $format /*, array $context = [] */); ++ public function supportsDecoding(string $format /*, array $context = [] */): bool; } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 7f86ed8d78..cf084423ec 100644 @@ -869,51 +934,52 @@ index 7f86ed8d78..cf084423ec 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 a241215133..2ed1af7a02 100644 +index 1abdb634ce..d2e7f41562 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php -@@ -136,5 +136,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer - * {@inheritdoc} +@@ -138,5 +138,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer + * @param array $context */ -- public function supportsNormalization(mixed $data, string $format = null) -+ public function supportsNormalization(mixed $data, string $format = null): bool +- 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; -@@ -144,5 +144,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer +@@ -146,5 +146,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'])) { -@@ -277,5 +277,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer +@@ -280,5 +280,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer * {@inheritdoc} */ - 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)) { -@@ -339,5 +339,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer +@@ -342,5 +342,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; - + /** -@@ -346,10 +346,10 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer +@@ -349,5 +349,5 @@ 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; - + /** - * {@inheritdoc} +@@ -356,5 +356,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer + * @param array $context */ -- public function supportsDenormalization(mixed $data, string $type, string $format = null) -+ public function supportsDenormalization(mixed $data, string $type, string $format = null): bool +- 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)); -@@ -359,5 +359,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer +@@ -364,5 +364,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer * {@inheritdoc} */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) @@ -921,7 +987,7 @@ index a241215133..2ed1af7a02 100644 { if (!isset($context['cache_key'])) { diff --git a/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php b/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php -index 5e94400b80..726d89cbb1 100644 +index 1c708738a1..3b6c9d5056 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php +++ b/src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php @@ -45,5 +45,5 @@ interface DenormalizerInterface @@ -929,16 +995,16 @@ index 5e94400b80..726d89cbb1 100644 */ - 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; - + /** -@@ -56,4 +56,4 @@ interface DenormalizerInterface +@@ -57,4 +57,4 @@ interface DenormalizerInterface * @return bool */ -- public function supportsDenormalization(mixed $data, string $type, string $format = null); -+ public function supportsDenormalization(mixed $data, string $type, string $format = null): bool; +- 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; } diff --git a/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php b/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php -index 30eeafb47b..a7a60ad2f2 100644 +index 741f19e50b..acf3be931b 100644 --- a/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php +++ b/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php @@ -37,5 +37,5 @@ interface NormalizerInterface @@ -946,13 +1012,13 @@ index 30eeafb47b..a7a60ad2f2 100644 */ - 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; - + /** -@@ -47,4 +47,4 @@ interface NormalizerInterface +@@ -48,4 +48,4 @@ interface NormalizerInterface * @return bool */ -- public function supportsNormalization(mixed $data, string $format = null); -+ public function supportsNormalization(mixed $data, string $format = null): bool; +- public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */); ++ public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool; } diff --git a/src/Symfony/Component/Templating/Helper/HelperInterface.php b/src/Symfony/Component/Templating/Helper/HelperInterface.php index 5dade65db5..db0d0a00ea 100644 @@ -963,7 +1029,7 @@ index 5dade65db5..db0d0a00ea 100644 */ - public function getName(); + public function getName(): string; - + /** diff --git a/src/Symfony/Component/Translation/Extractor/AbstractFileExtractor.php b/src/Symfony/Component/Translation/Extractor/AbstractFileExtractor.php index 4c088b94f9..86107a636d 100644 @@ -974,7 +1040,7 @@ index 4c088b94f9..86107a636d 100644 */ - abstract protected function canBeExtracted(string $file); + abstract protected function canBeExtracted(string $file): bool; - + /** * @return iterable */ @@ -1013,3 +1079,35 @@ index 46432f2f4c..47ac39d5e3 100644 + public function getTargets(): string|array { return self::PROPERTY_CONSTRAINT; +diff --git a/src/Symfony/Component/Workflow/Event/Event.php b/src/Symfony/Component/Workflow/Event/Event.php +index cd7fab7896..b340eba38e 100644 +--- a/src/Symfony/Component/Workflow/Event/Event.php ++++ b/src/Symfony/Component/Workflow/Event/Event.php +@@ -42,5 +42,5 @@ class Event extends BaseEvent + * @return Marking + */ +- public function getMarking() ++ public function getMarking(): Marking + { + return $this->marking; +@@ -50,5 +50,5 @@ class Event extends BaseEvent + * @return object + */ +- public function getSubject() ++ public function getSubject(): object + { + return $this->subject; +@@ -58,5 +58,5 @@ class Event extends BaseEvent + * @return Transition|null + */ +- public function getTransition() ++ public function getTransition(): ?Transition + { + return $this->transition; +@@ -71,5 +71,5 @@ class Event extends BaseEvent + * @return string + */ +- public function getWorkflowName() ++ public function getWorkflowName(): string + { + return $this->workflow->getName(); diff --git a/.github/get-modified-packages.php b/.github/get-modified-packages.php index a3682af0b9d1a..ed31942002182 100644 --- a/.github/get-modified-packages.php +++ b/.github/get-modified-packages.php @@ -50,9 +50,9 @@ function getPackageType(string $packageDir): string $modifiedPackages = []; foreach ($modifiedFiles as $file) { foreach ($allPackages as $package) { - if (0 === strpos($file, $package)) { + if (str_starts_with($file, $package)) { $modifiedPackages[$package] = true; - if ('LICENSE' === substr($file, -7)) { + if (str_ends_with($file, 'LICENSE')) { /* * There is never a reason to modify the LICENSE file, this diff * must be adding a new package diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index d3f97a643af4a..bf62788b7a81e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -20,7 +20,7 @@ jobs: strategy: matrix: - php: ['8.0'] + php: ['8.1'] services: postgres: @@ -129,7 +129,7 @@ jobs: uses: shivammathur/setup-php@v2 with: coverage: "none" - extensions: "json,couchbase,memcached,mongodb-1.10.0,redis-5.3.4,rdkafka,xsl,ldap" + extensions: "json,couchbase,memcached,mongodb-1.12.0,redis-5.3.4,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 @@ -153,7 +153,7 @@ jobs: echo COMPOSER_ROOT_VERSION=$COMPOSER_ROOT_VERSION >> $GITHUB_ENV echo "::group::composer update" - composer require --dev --no-update mongodb/mongodb:"1.9.1@dev|^1.9.1@stable" + composer require --dev --no-update mongodb/mongodb:"^1.11" composer update --no-progress --ansi echo "::endgroup::" @@ -176,11 +176,11 @@ jobs: POSTGRES_HOST: localhost #- name: Run HTTP push tests - # if: matrix.php == '8.0' + # if: matrix.php == '8.1' # run: | # [ -d .phpunit ] && mv .phpunit .phpunit.bak # wget -q https://github.com/symfony/binary-utils/releases/download/v0.1/vulcain_0.1.3_Linux_x86_64.tar.gz -O - | tar xz && mv vulcain /usr/local/bin - # docker run --rm -e COMPOSER_ROOT_VERSION -v $(pwd):/app -v $(which composer):/usr/local/bin/composer -v $(which vulcain):/usr/local/bin/vulcain -w /app php:8.0-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push + # docker run --rm -e COMPOSER_ROOT_VERSION -v $(pwd):/app -v $(which composer):/usr/local/bin/composer -v $(which vulcain):/usr/local/bin/vulcain -w /app php:8.1-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push # sudo rm -rf .phpunit # [ -d .phpunit.bak ] && mv .phpunit.bak .phpunit diff --git a/.github/workflows/intl-data-tests.yml b/.github/workflows/intl-data-tests.yml index a598db020e652..065c1a081cc1f 100644 --- a/.github/workflows/intl-data-tests.yml +++ b/.github/workflows/intl-data-tests.yml @@ -46,7 +46,7 @@ jobs: coverage: "none" extensions: "zip,intl-${{env.SYMFONY_ICU_VERSION}}" ini-values: "memory_limit=-1" - php-version: "8.0" + php-version: "8.1" - name: Install dependencies run: | diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 8dfcf9c865571..c3ad676bab3a4 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -24,7 +24,7 @@ jobs: matrix: include: - php: '8.1' - - php: '8.0' + - php: '8.1' mode: high-deps - php: '8.1' mode: low-deps @@ -144,8 +144,8 @@ jobs: patch -sp1 < .github/expected-missing-return-types.diff git add . composer install -q --optimize-autoloader - SYMFONY_PATCH_TYPE_DECLARATIONS='force=2&php=8.0' php .github/patch-types.php - SYMFONY_PATCH_TYPE_DECLARATIONS='force=2&php=8.0' php .github/patch-types.php # ensure the script is idempotent + SYMFONY_PATCH_TYPE_DECLARATIONS='force=2&php=8.1' php .github/patch-types.php + SYMFONY_PATCH_TYPE_DECLARATIONS='force=2&php=8.1' php .github/patch-types.php # ensure the script is idempotent git diff --exit-code - name: Run tests @@ -222,12 +222,12 @@ jobs: script -e -c './phpunit --group tty' /dev/null - name: Run tests with SIGCHLD enabled PHP - if: "matrix.php == '8.0' && ! matrix.mode" + if: "matrix.php == '8.1' && ! matrix.mode" run: | mkdir build cd build - wget -q https://github.com/symfony/binary-utils/releases/download/v0.1/php-8.0.2-pcntl-sigchild.tar.bz2 - tar -xjf php-8.0.2-pcntl-sigchild.tar.bz2 + wget -q https://github.com/symfony/binary-utils/releases/download/v0.1/php-8.1.2-pcntl-sigchild.tar.bz2 + tar -xjf php-8.1.2-pcntl-sigchild.tar.bz2 cd .. ./build/php/bin/php ./phpunit --colors=always src/Symfony/Component/Process diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5084a871f4247..2bc2c558d7b9b 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,9 +1,27 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + if (!file_exists(__DIR__.'/src')) { exit(0); } +$fileHeaderComment = <<<'EOF' +This file is part of the Symfony package. + +(c) Fabien Potencier + +For the full copyright and license information, please view the LICENSE +file that was distributed with this source code. +EOF; + return (new PhpCsFixer\Config()) ->setRules([ '@PHP71Migration' => true, @@ -13,6 +31,8 @@ 'protected_to_private' => false, 'native_constant_invocation' => ['strict' => false], 'nullable_type_declaration_for_default_null_value' => ['use_nullable_type_declaration' => false], + 'header_comment' => ['header' => $fileHeaderComment], + 'modernize_strpos' => true, ]) ->setRiskyAllowed(true) ->setFinder( diff --git a/CHANGELOG-6.1.md b/CHANGELOG-6.1.md new file mode 100644 index 0000000000000..e8cacc83e6211 --- /dev/null +++ b/CHANGELOG-6.1.md @@ -0,0 +1,127 @@ +CHANGELOG for 6.1.x +=================== + +This changelog references the relevant changes (bug and security fixes) done +in 6.1 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.1.0...v6.1.1 + +* 6.1.0-BETA1 (2022-04-15) + + * feature #44798 [FrameworkBundle] Integrate the HtmlSanitizer component (tgalopin, wouterj) + * feature #46045 [Translation] Improve LocaleSwitcher a bit (nicolas-grekas) + * feature #42403 [Validator] Define which collection keys should be checked for uniqueness (wkania) + * feature #44405 [Routing] Allow using services in the route condition (renanbr) + * feature #46009 [FrameworkBundle] Add support for first-class callable route controller in MicroKernelTrait (fancyweb) + * feature #44155 [FrameworkBundle] Add semaphore configuration (jderusse) + * feature #45803 [Routing] Add EnumRequirement to help generate route requirements from a \BackedEnum (fancyweb) + * feature #45724 [FrameworkBundle] Add support to set BinaryFileResponse::trustXSendfileTypeHeader over config (alexander-schranz) + * feature #45092 [HttpFoundation] Send `Content-Length` when calling `Response::send()` and the content is a non-empty string (nicolas-grekas) + * feature #45967 [Messenger] Consume a PSR-14 dispatcher for dispatching events (derrabus) + * feature #45951 [Notifier] [OvhCloud] Add `no_stop_clause` to DSN (alamirault) + * feature #45795 [ExpressionLanguage] Add support for null-safe operator (mytuny) + * feature #45605 [Form] Add prototype_options to CollectionType (michaelKaefer) + * feature #45912 [ExpressionLanguage] Add some more operators (fabpot) + * feature #45656 [Serializer] Add serializer profiler (mtarld) + * feature #45072 [Validator] Allow creating constraints with required arguments (norkunas) + * feature #43239 [Finder] Look for gitignore patterns up to git root (julienfalque) + * feature #45845 [TwigBundle]  Pre-compile only *.twig files in cache warmup (GromNaN) + * feature #44446 [Mailer] Improve extensibility of `EsmtpTransport` (ampaze) + * feature #45226 [PhpUnitBridge] Add option `ignoreFile` to configure a file that lists deprecation messages to ignore (mondrake) + * feature #43163 [Messenger] Add Redis Sentinel support (norbertschultheisz) + * feature #43701 [HttpKernel] Simplifying Bundle/Extension config definition (yceruto) + * feature #45873 [HttpFoundation] Allow dynamic session "ttl" when using a remote storage (nicolas-grekas) + * feature #45878 [DependencyInjection] Add argument type `closure` to help passing closures to services (nicolas-grekas) + * feature #44898 [Ldap] LDAP authentication should return a meaningful error when the LDAP server is unavailable (Jayfrown) + * feature #45090 [Validator] Improve Image constraint invalid mime type message (fancyweb) + * feature #42997 [Cache] Improve reliability and performance of `TagAwareAdapter` by making tag versions an integral part of item value (Sergey Belyshkin, nicolas-grekas) + * feature #45512 [DependencyInjection] Allow using expressions as service factories (nicolas-grekas, jvasseur) + * feature #45273 [Messenger] Allow AsMessageHandler attribute on methods (mjpvandenberg, fabpot) + * feature #44284 [SecurityBundle] Display the inherited roles of the logged-in user in the WDT (jmsche) + * feature #44303 Add Engagespot bridge (danut007ro) + * feature #44532 Handle CSV DSN in ZookeeperStore (qkdreyer) + * feature #45047 [Notifier] Use Importance level to set flash message type (benr77, fabpot) + * feature #45166 [HttpFoundation] add stale while revalidate cache header (remieuronews) + * feature #45195 [Notifier] Add Sendberry notifier bridge (StaffNowa) + * feature #45793 [FrameworkBundle][Translation] add `LocaleSwitcher` service (kbond) + * feature #45833 [HttpKernel] Add Http Status 423 LockedHttpException (xosofox) + * feature #45705 [FrameworkBundle] Deprecate the messenger.reset_on_message config option (upyx) + * feature #45812 [HttpClient] Improve default content-type handling (nicolas-grekas) + * feature #45783 [DependencyInjection] adjust `Autowire` attribute implementation (kbond) + * feature #44171 [Config] Add comment on array methods (jderusse) + * feature #45657 [DependencyInjection] add `Autowire` parameter attribute (kbond) + * feature #45725 [Finder] Fix SplFileInfo PHPDoc (InvisibleSmiley) + * feature #44948 [Console] Add completion values to input definition (GromNaN) + * feature #45745 [ErrorHandler][HttpKernel] Read SYMFONY_IDE to render exception in case of fatal error (GromNaN) + * feature #45765 Mailer - Display email recipients in Profiler (raziel057) + * feature #45094 Add generics to ArgumentMetadata::getAttributes (Seldaek) + * feature #45761 Throw access denied if CurrentUser cannot be resolved instead of a 500 (Seldaek) + * feature #45680 [DependencyInjection] use `#[Required]` for `ServiceSubscriberTrait::setContainer()` (kbond) + * feature #45624 [Config] Allow using environment variables in `EnumNode` (ecourtial) + * feature #45484 Make constraint violation interfaces stringable (HypeMC) + * feature #43931 [HttpClient][WebProfilerBundle] Add button to copy a request as a cURL command (Deuchnord) + * feature #45515 [BrowserKit] Add `toArray` to `Response` (HypeMC) + * feature #45658 [Routing] Avoid double encoded slashes in query parameters (usu) + * feature #45062 [PropertyInfo] Add PHP 8.0 promoted properties `@param` mutation support to PhpDocExtractor (raphaelvoisin) + * feature #44522 [Messenger] add TransportMessageIdStamp to RedisSender (GaryPEGEOT) + * feature #45623 [Validator] Deprecate constraint "ExpressionLanguageSyntax", use "ExpressionSyntax" instead (mpiot) + * feature #45563 Deprecate requiring the "symfony/symfony" package (nicolas-grekas) + * feature #45616 [HttpClient] Remove credentials from requests redirected to same host but different port (GromNaN) + * feature #45377 Bump minimum version of PHP to 8.1 (nicolas-grekas) + * feature #45421 [Translation] Add the possibilty to export xliff translation with the .xliff suffix (DanielBadura) + * feature #45152 Ability to customize payload when sending mail through mailjet+api (gam6itko) + * feature #44665 [HttpKernel] Add the UidValueResolver argument value resolver (fancyweb) + * feature #44073 [ExpressionLanguage] Support lexing numbers with underscores and decimals with no leading zero (fancyweb) + * feature #44721 [Serializer] Deprecate support for abstract uid denormalization in UidNormalizer (fancyweb) + * feature #44615 [Routing] Support the "attribute" type (alias of "annotation") in annotation loaders (fancyweb) + * feature #45265 [HttpKernel] Add Profiler::isEnabled() method (Bilge) + * feature #45449 [Mime] Added getter for "TextPart::$name" (MasterRO94) + * feature #45402 make Message classes extensible (bitgandtter) + * feature #45476 [HttpKernel] Deprecate StreamedResponseListener, it serves no purpose anymore (nicolas-grekas) + * feature #45436 [Messenger] Support setting `connection_name` for AMQP (a.dmitryuk) + * feature #45450 [DependencyInjection] Add an env function to DI expression language (jvasseur) + * feature #45388 [Mailer] Allow manually start() of SmtpTransport (jannick-holm) + * feature #45376 [Mime] Fix embed logic for background attributes (flack) + * feature #45360 [ErrorHandler] trigger deprecations for ``@final`` properties (nicolas-grekas, fancyweb) + * feature #45371 [Validator] Deprecate `Constraint::$errorNames` in favor of `Constraint::ERROR_NAMES` (nicolas-grekas) + * feature #44692 [Cache][FrameworkBundle] add `cache:pool:invalidate-tags` command (kbond) + * feature #45361 [Console] Deprecate the `$defaultName` property (derrabus) + * feature #45313 [Cache] Add support for ACL auth in RedisAdapter (gam6itko) + * feature #45303 [ErrorHandler] Report overridden @final constants (fancyweb) + * feature #44484 [Translation] [Loco] Send `If-Modified-Since` header when possible (Kocal) + * feature #45307 [Mailer] Allow manually stop() of SmtpTransport (dvaeversted) + * feature #43973 [Serializer] Add context builders (mtarld) + * feature #45222 [Mailer] Implement EmailTags for Amazon Mailer (driesvints, kbond) + * feature #44670 [SecurityBundle] Allow to specify a RequestMatcher directly in an ACL definition (TristanPouliquen) + * feature #45139 [Notifier] smsapi-notifier `fast` option to sending message with the highest priority (marphi) + * feature #45155 [Serializer] Set context annotation as not final (benjaminmal) + * feature #44503 [FrameworkBundle] Allow PHP configuration in config/packages by default (dreadnip) + * feature #45101 [Form] Add inputmode attribute on NumberType (welcoMattic) + * feature #45075 [Routing] Enrich MissingMandatoryParametersException (adrienlucas) + * feature #45064 [Messenger] Add sessionToken option to SQS transport (filkaris) + * feature #44917 [Mailer] Add downloadable attachments to profiler (dbrekelmans) + * feature #45054 [Routing] Allow using UTF-8 parameter names (nicolas-grekas) + * feature #44360 [Notifier] [Bridge] [KazInfoTeh] added the bridge (taranovegor) + * feature #44874 [Notifier] Added 46elks notifier bridge (jongotlin) + * feature #44913 [Notifier] Add Orange SMS bridge (enigma972) + * feature #44971 [Messenger] Resolve handled classes when only method in tag is provided (angelov) + * feature #43982 [Messenger][Serializer] Deprecate "context aware" interfaces (mtarld) + * feature #44790 [Serializer] Give more hints when an attribute is not correctly used (lyrixx) + * feature #44831 [HttpKernel] Add a controller argument resolver for backed enums (ogizanagi) + * feature #44589 [Messenger] add SerializedMessageStamp (nikophil) + * feature #41750 [Yaml] Double-quote strings with single quote marks (Ostrzyciel) + * feature #44774 Add `exclude` to `TaggedIterator` and `TaggedLocator` (ruudk) + * feature #44681 [HtmlSanitizer] Introduce HtmlSanitizer component (tgalopin) + * feature #44311 [Mime] add DraftEmail (kbond) + * feature #44746 [Console] Add method `__toString()` to `InputInterface` (boesing) + * feature #44568 [HttpClient] Allow yielding Exception from MockResponse's $body to mock transport errors (fancyweb) + * feature #44672 [Translation] Translatable parameters (sylfabre) + * feature #44451 [PropertyInfo] Add support for phpDocumentor and PHPStan pseudo-types (EmilMassey) + * feature #44575 [Framework] Read env var SYMFONY_IDE by default for framework.ide (GromNaN) + * feature #43641 [Console] Issue 43602 : Add fish completion (guillaume-a) + * feature #44150 [Assets] Accept empty `base_url`, in order to simplify local dev configuration. (GromNaN) + * feature #44137 [Mailer] [Mailgun] Allow multiple TagHeaders with MailgunApiTransport (starred-gijs) + * feature #44543 [HttpFoundation] Update cookie date time format (chapterjason) + * feature #44483 [HttpKernel][WebProfilerBundle] adding xdebug_info page to webprofilerbundle (chr-hertel) + diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e2781d32f4a30..bd30bf752af21 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -102,6 +102,7 @@ The Symfony Connect username in parenthesis allows to get more information - Henrik Westphal (snc) - Dariusz Górecki (canni) - Fran Moreno (franmomu) + - HypeMC (hypemc) - Jérôme Vasseur (jvasseur) - Mathieu Santostefano (welcomattic) - Dariusz Ruminski @@ -111,19 +112,18 @@ The Symfony Connect username in parenthesis allows to get more information - Daniel Holmes (dholmes) - Sebastiaan Stok (sstok) - Alexandre Daubois (alexandre-daubois) - - HypeMC (hypemc) - Toni Uebernickel (havvg) - Bart van den Burg (burgov) - Jordan Alliot (jalliot) - John Wards (johnwards) - Tomas Norkūnas (norkunas) - Julien Falque (julienfalque) + - Vincent Langlet (deviling) - Baptiste Clavié (talus) - Massimiliano Arione (garak) - Mathias Arlaud (mtarld) - Antoine Hérault (herzult) - Paráda József (paradajozsef) - - Vincent Langlet (deviling) - Arnaud Le Blanc (arnaud-lb) - Przemysław Bogusz (przemyslaw-bogusz) - Maxime STEINHAUSSER @@ -153,6 +153,7 @@ The Symfony Connect username in parenthesis allows to get more information - Teoh Han Hui (teohhanhui) - Colin Frei - Javier Spagnoletti (phansys) + - Gary PEGEOT (gary-p) - Ruud Kamphuis (ruudk) - Joshua Thijssen - Daniel Wehner (dawehner) @@ -161,7 +162,6 @@ The Symfony Connect username in parenthesis allows to get more information - Gordon Franke (gimler) - Saif Eddin Gmati (azjezz) - Richard van Laak (rvanlaak) - - Gary PEGEOT (gary-p) - Jesse Rushlow (geeshoe) - Fabien Pennequin (fabienpennequin) - Olivier Dolbeau (odolbeau) @@ -355,6 +355,7 @@ The Symfony Connect username in parenthesis allows to get more information - Sébastien Lavoie (lavoiesl) - Dariusz - Farhad Safarov (safarov) + - Hugo Alliaume (kocal) - BoShurik - Thomas Lallement (raziel057) - Michael Voříšek @@ -418,7 +419,6 @@ The Symfony Connect username in parenthesis allows to get more information - Christopher Davis (chrisguitarguy) - Dmitriy Mamontov (mamontovdmitriy) - Ben Ramsey (ramsey) - - Hugo Alliaume (kocal) - Laurent Masforné (heisenberg) - Sergey (upyx) - Giorgio Premi @@ -441,6 +441,7 @@ The Symfony Connect username in parenthesis allows to get more information - Iker Ibarguren (ikerib) - Bob van de Vijver (bobvandevijver) - Peter Kruithof (pkruithof) + - Antoine Lamirault - Michael Holm (hollo) - Arjen van der Meijden - Markus Fasselt (digilist) @@ -478,6 +479,7 @@ The Symfony Connect username in parenthesis allows to get more information - Andrey Esaulov (andremaha) - Grégoire Passault (gregwar) - Jerzy Zawadzki (jzawadzki) + - Phil Taylor (prazgod) - Ismael Ambrosi (iambrosi) - Craig Duncan (duncan3dc) - Emmanuel BORGES (eborges78) @@ -585,8 +587,6 @@ The Symfony Connect username in parenthesis allows to get more information - Loïc Chardonnet (gnusat) - Marek Kalnik (marekkalnik) - Vyacheslav Salakhutdinov (megazoll) - - Antoine Lamirault - - Phil Taylor (prazgod) - Hassan Amouhzi - Daniel Gorgan - Tamas Szijarto @@ -618,6 +618,7 @@ The Symfony Connect username in parenthesis allows to get more information - Xavier HAUSHERR - Albert Jessurum (ajessu) - Laszlo Korte + - Jonathan Scheiber (jmsche) - Miha Vrhovnik - Alessandro Desantis - hubert lecorche (hlecorche) @@ -688,6 +689,7 @@ The Symfony Connect username in parenthesis allows to get more information - Boris Vujicic (boris.vujicic) - Chris Sedlmayr (catchamonkey) - Indra Gunawan (indragunawan) + - Jérôme Tanghe (deuchnord) - Mathias STRASSER (roukmoute) - simon chrzanowski (simonch) - Kamil Kokot (pamil) @@ -831,7 +833,6 @@ The Symfony Connect username in parenthesis allows to get more information - Stefan Kruppa - jhonnyL - sasezaki - - Jonathan Scheiber (jmsche) - Kristof Van Cauwenbergh (kristofvc) - Dawid Pakuła (zulusx) - Marco Lipparini (liarco) @@ -1156,6 +1157,7 @@ The Symfony Connect username in parenthesis allows to get more information - Dominik Ritter (dritter) - Sebastian Grodzicki (sgrodzicki) - Florian Caron (shalalalala) + - Eric COURTIAL - Jeroen van den Enden (stoefke) - Aurélien Fontaine - Pascal Helfenstein @@ -1220,7 +1222,6 @@ The Symfony Connect username in parenthesis allows to get more information - Jérémy REYNAUD (babeuloula) - Mathieu Rochette (mathroc) - Victor Garcia - - Jérôme Tanghe (deuchnord) - Marek Víger (freezy) - Andrew Hilobok (hilobok) - Noah Heck (myesain) @@ -1342,6 +1343,7 @@ The Symfony Connect username in parenthesis allows to get more information - Matt Robinson (inanimatt) - Aleksey Podskrebyshev - Calin Mihai Pristavu + - Gabrielle Langer - David Marín Carreño (davefx) - Fabien LUCAS (flucas2) - Ondrej Machulda (ondram) @@ -1763,6 +1765,7 @@ The Symfony Connect username in parenthesis allows to get more information - Vladimir Luchaninov (luchaninov) - spdionis - rchoquet + - rvoisin - gitlost - Taras Girnyk - Dmitry Derepko @@ -2342,6 +2345,7 @@ The Symfony Connect username in parenthesis allows to get more information - Romain Dorgueil - Christopher Parotat - Dennis Haarbrink + - Urban Suppiger - me_shaon - 蝦米 - Grayson Koonce (breerly) @@ -2822,6 +2826,7 @@ The Symfony Connect username in parenthesis allows to get more information - Ikko Ashimine - Erwin Dirks - Brad Jones + - Markus Ramšak - Billie Thompson - lol768 - jamogon @@ -2898,7 +2903,6 @@ The Symfony Connect username in parenthesis allows to get more information - bokonet - Arrilot - ampaze - - Gabrielle Langer - Chris McGehee - Markus Staab - Pierre-Louis LAUNAY @@ -2920,6 +2924,7 @@ The Symfony Connect username in parenthesis allows to get more information - Lebnik - nsbx - Shude + - RTUnreal - Richard Hodgson - Sven Fabricius - Ondřej Führer diff --git a/README.md b/README.md index fb519d5f0e2ab..31b8b7c62a75f 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,12 @@ Installation Sponsor ------- -Symfony 6.0 is [backed][27] by [SensioLabs][28]. +Symfony 6.1 is [backed][27] by [basecom][28]. -As the creator of Symfony, SensioLabs supports companies using Symfony, with an -offering encompassing consultancy, expertise, services, training, and technical -assistance to ensure the success of web application development projects. +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. Help Symfony by [sponsoring][29] its development! @@ -87,5 +88,5 @@ 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://sensiolabs.com/ +[28]: https://www.basecom.de/ [29]: https://symfony.com/sponsor diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 5f617233855b7..718b729e55a2c 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -149,7 +149,7 @@ Inflector Ldap ---- -* Remove `LdapAuthenticator::createAuthenticatedToken()`, use `LdapAuthenticator::createToken()` instead + * Remove `LdapAuthenticator::createAuthenticatedToken()`, use `LdapAuthenticator::createToken()` instead Lock ---- diff --git a/UPGRADE-6.1.md b/UPGRADE-6.1.md new file mode 100644 index 0000000000000..1b66c0ece04dc --- /dev/null +++ b/UPGRADE-6.1.md @@ -0,0 +1,50 @@ +UPGRADE FROM 6.0 to 6.1 +======================= + +All components +-------------- + + * Deprecate requiring the "symfony/symfony" package; replace it with standalone components instead + * Public and protected properties are now considered final; + instead of overriding a property, consider setting its value in the constructor + +Console +------- + + * Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead + * Add argument `$suggestedValues` to `Command::addArgument` and `Command::addOption` + * Add argument `$suggestedValues` to `InputArgument` and `InputOption` constructors + +DependencyInjection +------------------- + + * Deprecate `ReferenceSetArgumentTrait` + +FrameworkBundle +--------------- + + * Deprecate the `reset_on_message` config option. It can be set to `true` only and does nothing now. + To prevent services resetting after each message the "--no-reset" option in "messenger:consume" command can be set + +HttpKernel +---------- + + * Deprecate StreamedResponseListener, it's not needed anymore + +Serializer +---------- + + * Deprecate `ContextAwareNormalizerInterface`, use `NormalizerInterface` instead + * Deprecate `ContextAwareDenormalizerInterface`, use `DenormalizerInterface` instead + * Deprecate `ContextAwareEncoderInterface`, use `EncoderInterface` instead + * Deprecate `ContextAwareDecoderInterface`, use `DecoderInterface` instead + * Deprecate supporting denormalization for `AbstractUid` in `UidNormalizer`, use one of `AbstractUid` child class instead + * Deprecate denormalizing to an abstract class in `UidNormalizer` + +Validator +--------- + + * Deprecate `Constraint::$errorNames`, use `Constraint::ERROR_NAMES` instead + * Deprecate constraint `ExpressionLanguageSyntax`, use `ExpressionSyntax` instead + * Implementing the `ConstraintViolationInterface` or `ConstraintViolationListInterface` + without implementing the `__toString()` method is deprecated diff --git a/composer.json b/composer.json index 0c750c584e9ca..87dca514ffa16 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "symfony/translation-implementation": "2.3|3.0" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "composer-runtime-api": ">=2.1", "ext-xml": "*", "friendsofphp/proxy-manager-lts": "^1.0.2", @@ -51,7 +51,6 @@ "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php81": "^1.23", "symfony/polyfill-uuid": "^1.15" }, "replace": { @@ -155,7 +154,7 @@ "egulias/email-validator": "~3.0.0", "masterminds/html5": "<2.6", "phpdocumentor/reflection-docblock": "<5.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/type-resolver": "<1.5.1", "ocramius/proxy-manager": "<2.1", "phpunit/phpunit": "<5.4.3" }, @@ -176,6 +175,9 @@ "files": [ "src/Symfony/Component/String/Resources/functions.php" ], + "classmap": [ + "src/Symfony/Component/Cache/Traits/ValueWrapper.php" + ], "exclude-from-classmap": [ "**/Tests/" ] @@ -191,7 +193,7 @@ "url": "src/Symfony/Contracts", "options": { "versions": { - "symfony/contracts": "3.0.x-dev" + "symfony/contracts": "3.1.x-dev" } } }, diff --git a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php index e90c82af15b4a..91cc25026a8b6 100644 --- a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php +++ b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php @@ -24,7 +24,7 @@ */ class ProxyCacheWarmer implements CacheWarmerInterface { - private $registry; + private ManagerRegistry $registry; public function __construct(ManagerRegistry $registry) { diff --git a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php index 0b979dbea3d47..d262c5e3591e4 100644 --- a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php +++ b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php @@ -33,7 +33,7 @@ class ContainerAwareEventManager extends EventManager private array $initialized = []; private bool $initializedSubscribers = false; private array $methods = []; - private $container; + private ContainerInterface $container; /** * @param list $subscriberIds List of subscribers, subscriber ids, or [events, listener] tuples @@ -56,7 +56,7 @@ public function dispatchEvent($eventName, EventArgs $eventArgs = null): void return; } - $eventArgs = $eventArgs ?? EventArgs::getEmptyInstance(); + $eventArgs ??= EventArgs::getEmptyInstance(); if (!isset($this->initialized[$eventName])) { $this->initializeListeners($eventName); diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index 96bb9e843b7ca..7acc2e8e96e95 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -29,7 +29,7 @@ */ class DoctrineDataCollector extends DataCollector { - private $registry; + private ManagerRegistry $registry; private array $connections; private array $managers; private ?DebugDataHolder $debugDataHolder; diff --git a/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php b/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php index 7ccd1df106f70..98acccda50ba9 100644 --- a/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php +++ b/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php @@ -25,7 +25,7 @@ */ class ContainerAwareLoader extends Loader { - private $container; + private ContainerInterface $container; public function __construct(ContainerInterface $container) { diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php index 9730690009b0f..e191bea3ca16d 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php @@ -22,10 +22,10 @@ */ class DoctrineChoiceLoader extends AbstractChoiceLoader { - private $manager; + private ObjectManager $manager; private string $class; - private $idReader; - private $objectLoader; + private ?IdReader $idReader; + private ?EntityLoaderInterface $objectLoader; /** * Creates a new choice loader. diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php index 35b2430f781cb..15a685bbc9bef 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php @@ -24,8 +24,8 @@ */ class IdReader { - private $om; - private $classMetadata; + private ObjectManager $om; + private ClassMetadata $classMetadata; private bool $singleId; private bool $intId; private string $idField; diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index 5c74ad5ebabbb..b0121df1b1da5 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -31,7 +31,7 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface * * This property should only be accessed through queryBuilder. */ - private $queryBuilder; + private QueryBuilder $queryBuilder; public function __construct(QueryBuilder $queryBuilder) { diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 191a853ac5c93..f2e65a7f0a8c0 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -17,6 +17,17 @@ use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\MappingException; use Doctrine\Persistence\Proxy; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\Extension\Core\Type\DateIntervalType; +use Symfony\Component\Form\Extension\Core\Type\DateTimeType; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\TimeType; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\TypeGuess; @@ -39,7 +50,7 @@ public function __construct(ManagerRegistry $registry) public function guessType(string $class, string $property): ?TypeGuess { if (!$ret = $this->getMetadata($class)) { - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', [], Guess::LOW_CONFIDENCE); + return new TypeGuess(TextType::class, [], Guess::LOW_CONFIDENCE); } [$metadata, $name] = $ret; @@ -48,47 +59,32 @@ public function guessType(string $class, string $property): ?TypeGuess $multiple = $metadata->isCollectionValuedAssociation($property); $mapping = $metadata->getAssociationMapping($property); - return new TypeGuess('Symfony\Bridge\Doctrine\Form\Type\EntityType', ['em' => $name, 'class' => $mapping['targetEntity'], 'multiple' => $multiple], Guess::HIGH_CONFIDENCE); + return new TypeGuess(EntityType::class, ['em' => $name, 'class' => $mapping['targetEntity'], 'multiple' => $multiple], Guess::HIGH_CONFIDENCE); } - switch ($metadata->getTypeOfField($property)) { - case Types::ARRAY: - case Types::SIMPLE_ARRAY: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CollectionType', [], Guess::MEDIUM_CONFIDENCE); - case Types::BOOLEAN: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CheckboxType', [], Guess::HIGH_CONFIDENCE); - case Types::DATETIME_MUTABLE: - case Types::DATETIMETZ_MUTABLE: - case 'vardatetime': - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateTimeType', [], Guess::HIGH_CONFIDENCE); - case Types::DATETIME_IMMUTABLE: - case Types::DATETIMETZ_IMMUTABLE: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateTimeType', ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE); - case Types::DATEINTERVAL: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateIntervalType', [], Guess::HIGH_CONFIDENCE); - case Types::DATE_MUTABLE: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateType', [], Guess::HIGH_CONFIDENCE); - case Types::DATE_IMMUTABLE: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateType', ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE); - case Types::TIME_MUTABLE: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TimeType', [], Guess::HIGH_CONFIDENCE); - case Types::TIME_IMMUTABLE: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TimeType', ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE); - case Types::DECIMAL: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\NumberType', ['input' => 'string'], Guess::MEDIUM_CONFIDENCE); - case Types::FLOAT: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\NumberType', [], Guess::MEDIUM_CONFIDENCE); - case Types::INTEGER: - case Types::BIGINT: - case Types::SMALLINT: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\IntegerType', [], Guess::MEDIUM_CONFIDENCE); - case Types::STRING: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', [], Guess::MEDIUM_CONFIDENCE); - case Types::TEXT: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextareaType', [], Guess::MEDIUM_CONFIDENCE); - default: - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', [], Guess::LOW_CONFIDENCE); - } + return match ($metadata->getTypeOfField($property)) { + Types::ARRAY, + Types::SIMPLE_ARRAY => new TypeGuess(CollectionType::class, [], Guess::MEDIUM_CONFIDENCE), + Types::BOOLEAN => new TypeGuess(CheckboxType::class, [], Guess::HIGH_CONFIDENCE), + Types::DATETIME_MUTABLE, + Types::DATETIMETZ_MUTABLE, + 'vardatetime' => new TypeGuess(DateTimeType::class, [], Guess::HIGH_CONFIDENCE), + Types::DATETIME_IMMUTABLE, + Types::DATETIMETZ_IMMUTABLE => new TypeGuess(DateTimeType::class, ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE), + Types::DATEINTERVAL => new TypeGuess(DateIntervalType::class, [], Guess::HIGH_CONFIDENCE), + Types::DATE_MUTABLE => new TypeGuess(DateType::class, [], Guess::HIGH_CONFIDENCE), + Types::DATE_IMMUTABLE => new TypeGuess(DateType::class, ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE), + Types::TIME_MUTABLE => new TypeGuess(TimeType::class, [], Guess::HIGH_CONFIDENCE), + Types::TIME_IMMUTABLE => new TypeGuess(TimeType::class, ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE), + Types::DECIMAL => new TypeGuess(NumberType::class, ['input' => 'string'], Guess::MEDIUM_CONFIDENCE), + Types::FLOAT => new TypeGuess(NumberType::class, [], Guess::MEDIUM_CONFIDENCE), + Types::INTEGER, + Types::BIGINT, + Types::SMALLINT => new TypeGuess(IntegerType::class, [], Guess::MEDIUM_CONFIDENCE), + Types::STRING => new TypeGuess(TextType::class, [], Guess::MEDIUM_CONFIDENCE), + Types::TEXT => new TypeGuess(TextareaType::class, [], Guess::MEDIUM_CONFIDENCE), + default => new TypeGuess(TextType::class, [], Guess::LOW_CONFIDENCE), + }; } /** @@ -180,9 +176,9 @@ protected function getMetadata(string $class) foreach ($this->registry->getManagers() as $name => $em) { try { return $this->cache[$class] = [$em->getClassMetadata($class), $name]; - } catch (MappingException $e) { + } catch (MappingException) { // not an entity or mapped super class - } catch (LegacyMappingException $e) { + } catch (LegacyMappingException) { // not an entity or mapped super class, using Doctrine ORM 2.2 } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index f91d240451470..cd4dc42393059 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -156,7 +156,7 @@ public function configureOptions(OptionsResolver $resolver) $choiceValue = function (Options $options) { // If the entity has a single-column ID, use that ID as value if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isSingleId()) { - return ChoiceList::value($this, [$options['id_reader'], 'getIdValue'], $options['id_reader']); + return ChoiceList::value($this, $options['id_reader']->getIdValue(...), $options['id_reader']); } // Otherwise, an incrementing integer is used as value automatically diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index 6b73af766536f..d50ca5c339b95 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -82,7 +82,7 @@ public function getQueryBuilderPartsForCachingHash(object $queryBuilder): ?array return [ $queryBuilder->getQuery()->getSQL(), - array_map([$this, 'parameterToArray'], $queryBuilder->getParameters()->toArray()), + array_map($this->parameterToArray(...), $queryBuilder->getParameters()->toArray()), ]; } diff --git a/src/Symfony/Bridge/Doctrine/IdGenerator/UlidGenerator.php b/src/Symfony/Bridge/Doctrine/IdGenerator/UlidGenerator.php index 74dc9ec250f2b..95573309f9e4b 100644 --- a/src/Symfony/Bridge/Doctrine/IdGenerator/UlidGenerator.php +++ b/src/Symfony/Bridge/Doctrine/IdGenerator/UlidGenerator.php @@ -19,7 +19,7 @@ final class UlidGenerator extends AbstractIdGenerator { - private $factory; + private ?UlidFactory $factory; public function __construct(UlidFactory $factory = null) { diff --git a/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php b/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php index d72dc327c2fe8..8c366fd80d734 100644 --- a/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php +++ b/src/Symfony/Bridge/Doctrine/IdGenerator/UuidGenerator.php @@ -14,13 +14,16 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Id\AbstractIdGenerator; +use Symfony\Component\Uid\Factory\NameBasedUuidFactory; +use Symfony\Component\Uid\Factory\RandomBasedUuidFactory; +use Symfony\Component\Uid\Factory\TimeBasedUuidFactory; use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Uid\Uuid; final class UuidGenerator extends AbstractIdGenerator { - private $protoFactory; - private $factory; + private UuidFactory $protoFactory; + private UuidFactory|NameBasedUuidFactory|RandomBasedUuidFactory|TimeBasedUuidFactory $factory; private ?string $entityGetter = null; public function __construct(UuidFactory $factory = null) diff --git a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php index f9bd6e0cbcf73..b4cc571bb56fc 100644 --- a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +++ b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php @@ -37,9 +37,7 @@ public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch */ public function startQuery($sql, array $params = null, array $types = null): void { - if (null !== $this->stopwatch) { - $this->stopwatch->start('doctrine', 'doctrine'); - } + $this->stopwatch?->start('doctrine', 'doctrine'); if (null !== $this->logger) { $this->log($sql, null === $params ? [] : $this->normalizeParams($params)); @@ -51,9 +49,7 @@ public function startQuery($sql, array $params = null, array $types = null): voi */ public function stopQuery(): void { - if (null !== $this->stopwatch) { - $this->stopwatch->stop('doctrine'); - } + $this->stopwatch?->stop('doctrine'); } /** diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php index e06ba250b8e17..9b464351fa09d 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineClearEntityManagerWorkerSubscriber.php @@ -23,7 +23,7 @@ */ class DoctrineClearEntityManagerWorkerSubscriber implements EventSubscriberInterface { - private $managerRegistry; + private ManagerRegistry $managerRegistry; public function __construct(ManagerRegistry $managerRegistry) { diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php index de925284d09dc..105a1c6ffe76e 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php @@ -39,7 +39,7 @@ private function pingConnection(EntityManagerInterface $entityManager) try { $connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL()); - } catch (DBALException $e) { + } catch (DBALException) { $connection->close(); $connection->connect(); } diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index 9e294b2fdbd30..2126dad1bade6 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -31,7 +31,7 @@ */ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface { - private $entityManager; + private EntityManagerInterface $entityManager; public function __construct(EntityManagerInterface $entityManager) { @@ -212,7 +212,7 @@ private function getMetadata(string $class): ?ClassMetadata { try { return $this->entityManager->getClassMetadata($class); - } catch (MappingException|OrmMappingException $exception) { + } catch (MappingException|OrmMappingException) { return null; } } @@ -247,47 +247,33 @@ private function isAssociationNullable(array $associationMapping): bool */ private function getPhpType(string $doctrineType): ?string { - switch ($doctrineType) { - case Types::SMALLINT: - case Types::INTEGER: - return Type::BUILTIN_TYPE_INT; - - case Types::FLOAT: - return Type::BUILTIN_TYPE_FLOAT; - - case Types::BIGINT: - case Types::STRING: - case Types::TEXT: - case Types::GUID: - case Types::DECIMAL: - return Type::BUILTIN_TYPE_STRING; - - case Types::BOOLEAN: - return Type::BUILTIN_TYPE_BOOL; - - case Types::BLOB: - case Types::BINARY: - return Type::BUILTIN_TYPE_RESOURCE; - - case Types::OBJECT: - case Types::DATE_MUTABLE: - case Types::DATETIME_MUTABLE: - case Types::DATETIMETZ_MUTABLE: - case 'vardatetime': - case Types::TIME_MUTABLE: - case Types::DATE_IMMUTABLE: - case Types::DATETIME_IMMUTABLE: - case Types::DATETIMETZ_IMMUTABLE: - case Types::TIME_IMMUTABLE: - case Types::DATEINTERVAL: - return Type::BUILTIN_TYPE_OBJECT; - - case Types::ARRAY: - case Types::SIMPLE_ARRAY: - case 'json_array': - return Type::BUILTIN_TYPE_ARRAY; - } - - return null; + return match ($doctrineType) { + Types::SMALLINT, + Types::INTEGER => Type::BUILTIN_TYPE_INT, + Types::FLOAT => Type::BUILTIN_TYPE_FLOAT, + Types::BIGINT, + Types::STRING, + Types::TEXT, + Types::GUID, + Types::DECIMAL => Type::BUILTIN_TYPE_STRING, + Types::BOOLEAN => Type::BUILTIN_TYPE_BOOL, + Types::BLOB, + Types::BINARY => Type::BUILTIN_TYPE_RESOURCE, + Types::OBJECT, + Types::DATE_MUTABLE, + Types::DATETIME_MUTABLE, + Types::DATETIMETZ_MUTABLE, + 'vardatetime', + Types::TIME_MUTABLE, + Types::DATE_IMMUTABLE, + Types::DATETIME_IMMUTABLE, + Types::DATETIMETZ_IMMUTABLE, + Types::TIME_IMMUTABLE, + Types::DATEINTERVAL => Type::BUILTIN_TYPE_OBJECT, + Types::ARRAY, + Types::SIMPLE_ARRAY, + 'json_array' => Type::BUILTIN_TYPE_ARRAY, + default => null, + }; } } diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index 780999f4385ce..4d8dcb260467d 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -43,7 +43,7 @@ */ class DoctrineTokenProvider implements TokenProviderInterface, TokenVerifierInterface { - private $conn; + private Connection $conn; public function __construct(Connection $conn) { @@ -163,7 +163,7 @@ public function verifyToken(PersistentTokenInterface $token, string $tokenValue) // we also accept it as a valid value. try { $tmpToken = $this->loadTokenBySeries($tmpSeries); - } catch (TokenNotFoundException $e) { + } catch (TokenNotFoundException) { return false; } diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index b5ae03d549dee..f7baf6ba69b9d 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -32,7 +32,7 @@ */ class EntityUserProvider implements UserProviderInterface, PasswordUpgraderInterface { - private $registry; + private ManagerRegistry $registry; private ?string $managerName; private string $classOrAlias; private string $class; diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.php index 23977a3be9881..4ca941f10094d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.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\DataCollector; use Symfony\Component\HttpFoundation\Request; diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterMappingsPassTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterMappingsPassTest.php index c24c4b53005c9..fecc532a0b609 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterMappingsPassTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterMappingsPassTest.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\DependencyInjection\CompilerPass; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterUidTypePassTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterUidTypePassTest.php index 9b87c051702ee..03398e4245ebe 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterUidTypePassTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterUidTypePassTest.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\DependencyInjection\CompilerPass; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php index 17df373369d95..c993b89610184 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -79,7 +79,6 @@ public function testFixManagersAutoMappingsWithTwoAutomappings() $reflection = new \ReflectionClass(\get_class($this->extension)); $method = $reflection->getMethod('fixManagersAutoMappings'); - $method->setAccessible(true); $method->invoke($this->extension, $emConfigs, $bundles); } @@ -168,7 +167,6 @@ public function testFixManagersAutoMappings(array $originalEm1, array $originalE $reflection = new \ReflectionClass(\get_class($this->extension)); $method = $reflection->getMethod('fixManagersAutoMappings'); - $method->setAccessible(true); $newEmConfigs = $method->invoke($this->extension, $emConfigs, $bundles); @@ -186,7 +184,6 @@ public function testMappingTypeDetection() $reflection = new \ReflectionClass(\get_class($this->extension)); $method = $reflection->getMethod('detectMappingType'); - $method->setAccessible(true); // The ordinary fixtures contain annotation $mappingType = $method->invoke($this->extension, __DIR__.'/../Fixtures', $container); @@ -329,7 +326,6 @@ public function testBundleAutoMapping(string $bundle, string $expectedType, stri $reflection = new \ReflectionClass(\get_class($this->extension)); $method = $reflection->getMethod('getMappingDriverBundleConfigDefaults'); - $method->setAccessible(true); $this->assertSame( [ @@ -347,8 +343,6 @@ protected function invokeLoadCacheDriver(array $objectManager, ContainerBuilder { $method = new \ReflectionMethod($this->extension, 'loadObjectManagerCacheDriver'); - $method->setAccessible(true); - $method->invokeArgs($this->extension, [$objectManager, $container, $cacheName]); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index d1c2c04b88956..fd167bbe764ae 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -1310,10 +1310,7 @@ public function testLoaderCachingWithParameters() $this->assertSame($choiceList1, $choiceList3); } - /** - * @return MockObject&ManagerRegistry - */ - protected function createRegistryMock($name, $em): ManagerRegistry + protected function createRegistryMock($name, $em): MockObject&ManagerRegistry { $registry = $this->createMock(ManagerRegistry::class); $registry->expects($this->any()) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php index 46e85784e821b..d1243f5f03d44 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.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\Middleware\Debug; use Doctrine\DBAL\Configuration; @@ -62,12 +71,12 @@ public function provideExecuteMethod(): array { return [ 'executeStatement' => [ - static function(object $target, ...$args) { + static function (object $target, ...$args) { return $target->executeStatement(...$args); }, ], 'executeQuery' => [ - static function(object $target, ...$args): Result { + static function (object $target, ...$args): Result { return $target->executeQuery(...$args); }, ], @@ -148,13 +157,13 @@ public function provideEndTransactionMethod(): array { return [ 'commit' => [ - static function(Connection $conn): bool { + static function (Connection $conn): bool { return $conn->commit(); }, '"COMMIT"', ], 'rollback' => [ - static function(Connection $conn): bool { + static function (Connection $conn): bool { return $conn->rollBack(); }, '"ROLLBACK"', @@ -198,18 +207,18 @@ public function provideExecuteAndEndTransactionMethods(): array { return [ 'commit and exec' => [ - static function(Connection $conn, string $sql) { + static function (Connection $conn, string $sql) { return $conn->executeStatement($sql); }, - static function(Connection $conn): bool { + static function (Connection $conn): bool { return $conn->commit(); }, ], 'rollback and query' => [ - static function(Connection $conn, string $sql): Result { + static function (Connection $conn, string $sql): Result { return $conn->executeQuery($sql); }, - static function(Connection $conn): bool { + static function (Connection $conn): bool { return $conn->rollBack(); }, ], diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index 5d0a09f5f906a..c53a8d3c4dbea 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -128,9 +128,6 @@ public function testExtractWithEmbedded() $this->assertEquals($expectedTypes, $actualTypes); } - /** - * @requires PHP 8.1 - */ public function testExtractEnum() { if (!property_exists(Column::class, 'enumType')) { diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php index 4e75f41cb688a..9d8b9256f4c9d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.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 Security\RememberMe; use Doctrine\DBAL\DriverManager; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php index 034cd001aaebb..027e21e0dd58c 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php @@ -151,9 +151,6 @@ public function testLoadClassMetadata() $this->assertSame(AutoMappingStrategy::DISABLED, $noAutoMappingMetadata[0]->getAutoMappingStrategy()); } - /** - * @requires PHP 8.1 - */ public function testExtractEnum() { if (!property_exists(Column::class, 'enumType')) { diff --git a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php index 54f6d01ee4ea2..c47ade87d430f 100644 --- a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php +++ b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php @@ -80,7 +80,7 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?str try { return $this->getUidClass()::fromString($value)->$toString(); - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { throw ConversionException::conversionFailed($value, $this->getName()); } } diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php index ebfd5e62046d3..7d65c898ccb47 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php @@ -26,6 +26,10 @@ class UniqueEntity extends Constraint { public const NOT_UNIQUE_ERROR = '23bd9dbf-6b9b-41cd-a99e-4844bcf3077f'; + protected const ERROR_NAMES = [ + self::NOT_UNIQUE_ERROR => 'NOT_UNIQUE_ERROR', + ]; + public $message = 'This value is already used.'; public $service = 'doctrine.orm.validator.unique'; public $em = null; @@ -35,9 +39,10 @@ class UniqueEntity extends Constraint public $errorPath = null; public $ignoreNull = true; - protected static $errorNames = [ - self::NOT_UNIQUE_ERROR => 'NOT_UNIQUE_ERROR', - ]; + /** + * @deprecated since Symfony 6.1, use const ERROR_NAMES instead + */ + protected static $errorNames = self::ERROR_NAMES; /** * {@inheritdoc} diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index e56ff0f026175..1274607287560 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -27,7 +27,7 @@ */ class UniqueEntityValidator extends ConstraintValidator { - private $registry; + private ManagerRegistry $registry; public function __construct(ManagerRegistry $registry) { diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php index 28d5fccc7459c..3e7922ec79276 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php @@ -30,9 +30,6 @@ public function __construct(ManagerRegistry $registry) public function initialize(object $object) { - $manager = $this->registry->getManagerForClass(\get_class($object)); - if (null !== $manager) { - $manager->initializeObject($object); - } + $this->registry->getManagerForClass(\get_class($object))?->initializeObject($object); } } diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php index d1918fc6de45b..45758d74b8685 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php @@ -33,7 +33,7 @@ final class DoctrineLoader implements LoaderInterface { use AutoMappingTrait; - private $entityManager; + private EntityManagerInterface $entityManager; private ?string $classValidatorRegexp; public function __construct(EntityManagerInterface $entityManager, string $classValidatorRegexp = null) @@ -50,7 +50,7 @@ public function loadClassMetadata(ClassMetadata $metadata): bool $className = $metadata->getClassName(); try { $doctrineMetadata = $this->entityManager->getClassMetadata($className); - } catch (MappingException|OrmMappingException $exception) { + } catch (MappingException|OrmMappingException) { return false; } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index d482efec66af8..0b4ae502172cb 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "doctrine/event-manager": "~1.0", "doctrine/persistence": "^2", "symfony/deprecation-contracts": "^2.1|^3", diff --git a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php index 5a5dd1774a066..3168e2598b5dc 100644 --- a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php +++ b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Monolog\Command; use Monolog\Formatter\FormatterInterface; +use Monolog\Handler\HandlerInterface; use Monolog\Logger; use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; use Symfony\Bridge\Monolog\Handler\ConsoleHandler; @@ -32,8 +33,8 @@ class ServerLogCommand extends Command { private const BG_COLOR = ['black', 'blue', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow']; - private $el; - private $handler; + private ExpressionLanguage $el; + private HandlerInterface $handler; public function isEnabled(): bool { diff --git a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php index b7d2adc7dffa6..5b04c4a62434f 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php @@ -42,14 +42,14 @@ class ConsoleFormatter implements FormatterInterface ]; private array $options; - private $cloner; + private VarCloner $cloner; /** * @var resource|null */ private $outputBuffer; - private $dumper; + private CliDumper $dumper; /** * Available options: @@ -72,14 +72,14 @@ public function __construct(array $options = []) if (class_exists(VarCloner::class)) { $this->cloner = new VarCloner(); $this->cloner->addCasters([ - '*' => [$this, 'castObject'], + '*' => $this->castObject(...), ]); $this->outputBuffer = fopen('php://memory', 'r+'); if ($this->options['multiline']) { $output = $this->outputBuffer; } else { - $output = [$this, 'echoLine']; + $output = $this->echoLine(...); } $this->dumper = new CliDumper($output, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); diff --git a/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php index d895cac1d5c6b..e745afec13650 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/VarDumperFormatter.php @@ -19,7 +19,7 @@ */ class VarDumperFormatter implements FormatterInterface { - private $cloner; + private VarCloner $cloner; public function __construct(VarCloner $cloner = null) { diff --git a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php index 4a90ade3f8a2c..1fa698a9390b6 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php @@ -25,7 +25,7 @@ class ChromePhpHandler extends BaseChromePhpHandler { private array $headers = []; - private $response; + private Response $response; /** * Adds the headers to the response once it's created. diff --git a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php index 3c42efd8f510a..3c911f3cfa91d 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php @@ -43,7 +43,7 @@ */ class ConsoleHandler extends AbstractProcessingHandler implements EventSubscriberInterface { - private $output; + private ?OutputInterface $output; private array $verbosityLevelMap = [ OutputInterface::VERBOSITY_QUIET => Logger::ERROR, OutputInterface::VERBOSITY_NORMAL => Logger::WARNING, diff --git a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php index 66dade361c641..86af1d21bd02d 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php @@ -47,7 +47,7 @@ class ElasticsearchLogstashHandler extends AbstractHandler private string $endpoint; private string $index; - private $client; + private HttpClientInterface $client; /** * @var \SplObjectStorage @@ -80,7 +80,7 @@ public function handle(array $record): bool public function handleBatch(array $records): void { - $records = array_filter($records, [$this, 'isHandling']); + $records = array_filter($records, $this->isHandling(...)); if ($records) { $this->sendToElasticsearch($records); diff --git a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/HttpCodeActivationStrategy.php b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/HttpCodeActivationStrategy.php index ddc5443b1ab18..fc78f2dc32c49 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/HttpCodeActivationStrategy.php +++ b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/HttpCodeActivationStrategy.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Monolog\Handler\FingersCrossed; use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Exception\HttpException; /** @@ -26,9 +27,9 @@ final class HttpCodeActivationStrategy implements ActivationStrategyInterface * @param array $exclusions each exclusion must have a "code" and "urls" keys */ public function __construct( - private $requestStack, + private RequestStack $requestStack, private array $exclusions, - private $inner, + private ActivationStrategyInterface $inner, ) { foreach ($exclusions as $exclusion) { if (!\array_key_exists('code', $exclusion)) { diff --git a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php index 22f54bdf6d2da..808d863cec663 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php +++ b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Monolog\Handler\FingersCrossed; use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Exception\HttpException; /** @@ -26,9 +27,9 @@ final class NotFoundActivationStrategy implements ActivationStrategyInterface private string $exclude; public function __construct( - private $requestStack, + private RequestStack $requestStack, array $excludedUrls, - private $inner + private ActivationStrategyInterface $inner ) { $this->exclude = '{('.implode('|', $excludedUrls).')}i'; } diff --git a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php index c9dc672c98021..b06f3244e73b8 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php @@ -25,7 +25,7 @@ class FirePHPHandler extends BaseFirePHPHandler { private array $headers = []; - private $response; + private Response $response; /** * Adds the headers to the response once it's created. diff --git a/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php index 07208979bc451..f0446b09f3169 100644 --- a/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php @@ -24,15 +24,15 @@ */ class MailerHandler extends AbstractProcessingHandler { - private $mailer; - private $messageTemplate; + private MailerInterface $mailer; + private \Closure|Email $messageTemplate; public function __construct(MailerInterface $mailer, callable|Email $messageTemplate, string|int $level = Logger::DEBUG, bool $bubble = true) { parent::__construct($level, $bubble); $this->mailer = $mailer; - $this->messageTemplate = !\is_callable($messageTemplate) || $messageTemplate instanceof \Closure ? $messageTemplate : \Closure::fromCallable($messageTemplate); + $this->messageTemplate = $messageTemplate instanceof Email ? $messageTemplate : $messageTemplate(...); } /** diff --git a/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php index 13961b1bec890..a129085c905e5 100644 --- a/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php @@ -24,7 +24,7 @@ */ class NotifierHandler extends AbstractHandler { - private $notifier; + private NotifierInterface $notifier; public function __construct(NotifierInterface $notifier, string|int $level = Logger::ERROR, bool $bubble = true) { @@ -46,7 +46,7 @@ public function handle(array $record): bool public function handleBatch(array $records): void { - if ($records = array_filter($records, [$this, 'isHandling'])) { + if ($records = array_filter($records, $this->isHandling(...))) { $this->notify($records); } } diff --git a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php index 0f2ae6cda0d74..bca5b948c6b9b 100644 --- a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php @@ -21,7 +21,7 @@ class DebugProcessor implements DebugLoggerInterface, ResetInterface { private array $records = []; private array $errorCount = []; - private $requestStack; + private ?RequestStack $requestStack; public function __construct(RequestStack $requestStack = null) { diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index 1fd9424f683a9..040ec44353576 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "monolog/monolog": "^1.25.1|^2", "symfony/service-contracts": "^1.1|^2|^3", "symfony/http-kernel": "^5.4|^6.0" diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index bdcd3ea0d7819..9b8a974ecc5e6 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.1 +--- + + * Add option `ignoreFile` to configure a file that lists deprecation messages to ignore + 6.0 --- diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 2aeae49d58fa9..48df274ebb948 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -136,6 +136,9 @@ public function handleError($type, $msg, $file, $line, $context = []) if ($deprecation->isMuted()) { return null; } + if ($this->getConfiguration()->isIgnoredDeprecation($deprecation)) { + return null; + } if ($this->getConfiguration()->isBaselineDeprecation($deprecation)) { return null; } @@ -331,9 +334,14 @@ private function displayDeprecations($groups, $configuration, $isFailing) $countsByCaller = $notice->getCountsByCaller(); arsort($countsByCaller); + $limit = 5; foreach ($countsByCaller as $method => $count) { if ('count' !== $method) { + if (!$limit--) { + fwrite($handle, " ...\n"); + break; + } fwrite($handle, sprintf(" %dx in %s\n", $count, preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method))); } } diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php index 4420ef3d0e46c..04d24883a9920 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php @@ -36,6 +36,11 @@ class Configuration */ private $verboseOutput; + /** + * @var string[] + */ + private $ignoreDeprecationPatterns = []; + /** * @var bool */ @@ -60,11 +65,12 @@ class Configuration * @param int[] $thresholds A hash associating groups to thresholds * @param string $regex Will be matched against messages, to decide whether to display a stack trace * @param bool[] $verboseOutput Keyed by groups + * @param string $ignoreFile The path to the ignore deprecation patterns file * @param bool $generateBaseline Whether to generate or update the baseline file * @param string $baselineFile The path to the baseline file * @param string|null $logFile The path to the log file */ - private function __construct(array $thresholds = [], $regex = '', $verboseOutput = [], $generateBaseline = false, $baselineFile = '', $logFile = null) + private function __construct(array $thresholds = [], $regex = '', $verboseOutput = [], $ignoreFile = '', $generateBaseline = false, $baselineFile = '', $logFile = null) { $groups = ['total', 'indirect', 'direct', 'self']; @@ -110,6 +116,25 @@ private function __construct(array $thresholds = [], $regex = '', $verboseOutput $this->verboseOutput[$group] = $status; } + if ($ignoreFile) { + if (!is_file($ignoreFile)) { + throw new \InvalidArgumentException(sprintf('The ignoreFile "%s" does not exist.', $ignoreFile)); + } + set_error_handler(static function ($t, $m) use ($ignoreFile, &$line) { + throw new \RuntimeException(sprintf('Invalid pattern found in "%s" on line "%d"', $ignoreFile, 1 + $line).substr($m, 12)); + }); + try { + foreach (file($ignoreFile) as $line => $pattern) { + if ('#' !== (trim($pattern)[0] ?? '#')) { + preg_match($pattern, ''); + $this->ignoreDeprecationPatterns[] = $pattern; + } + } + } finally { + restore_error_handler(); + } + } + if ($generateBaseline && !$baselineFile) { throw new \InvalidArgumentException('You cannot use the "generateBaseline" configuration option without providing a "baselineFile" configuration option.'); } @@ -165,6 +190,19 @@ public function tolerates(array $deprecationGroups) return true; } + public function isIgnoredDeprecation(Deprecation $deprecation): bool + { + if (!$this->ignoreDeprecationPatterns) { + return false; + } + $result = @preg_filter($this->ignoreDeprecationPatterns, '$0', $deprecation->getMessage()); + if (\PREG_NO_ERROR !== preg_last_error()) { + throw new \RuntimeException(preg_last_error_msg()); + } + + return (bool) $result; + } + /** * @return bool */ @@ -266,16 +304,17 @@ public static function fromUrlEncodedString($serializedConfiguration) { parse_str($serializedConfiguration, $normalizedConfiguration); foreach (array_keys($normalizedConfiguration) as $key) { - if (!\in_array($key, ['max', 'disabled', 'verbose', 'quiet', 'generateBaseline', 'baselineFile', 'logFile'], true)) { + if (!\in_array($key, ['max', 'disabled', 'verbose', 'quiet', 'ignoreFile', 'generateBaseline', 'baselineFile', 'logFile'], true)) { throw new \InvalidArgumentException(sprintf('Unknown configuration option "%s".', $key)); } } $normalizedConfiguration += [ - 'max' => [], + 'max' => ['total' => 0], 'disabled' => false, 'verbose' => true, 'quiet' => [], + 'ignoreFile' => '', 'generateBaseline' => false, 'baselineFile' => '', 'logFile' => null, @@ -297,9 +336,10 @@ public static function fromUrlEncodedString($serializedConfiguration) } return new self( - $normalizedConfiguration['max'] ?? [], + $normalizedConfiguration['max'], '', $verboseOutput, + $normalizedConfiguration['ignoreFile'], filter_var($normalizedConfiguration['generateBaseline'], \FILTER_VALIDATE_BOOLEAN), $normalizedConfiguration['baselineFile'], $normalizedConfiguration['logFile'] diff --git a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php index b309606d5bd4e..19408df6d2dfe 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.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\PhpUnit\Tests; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php index 5d36a43bff54f..2f59ae5b91e2e 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php @@ -391,6 +391,62 @@ public function testBaselineFileWriteError() $configuration->writeBaseline(); } + public function testExistingIgnoreFile() + { + $filename = $this->createFile(); + $ignorePatterns = [ + '/Test message .*/', + '/^\d* occurrences/', + ]; + file_put_contents($filename, implode("\n", $ignorePatterns)); + + $configuration = Configuration::fromUrlEncodedString('ignoreFile='.urlencode($filename)); + $trace = debug_backtrace(); + $this->assertTrue($configuration->isIgnoredDeprecation(new Deprecation('Test message 1', $trace, ''))); + $this->assertTrue($configuration->isIgnoredDeprecation(new Deprecation('Test message 2', $trace, ''))); + $this->assertFalse($configuration->isIgnoredDeprecation(new Deprecation('Test mexxage 3', $trace, ''))); + $this->assertTrue($configuration->isIgnoredDeprecation(new Deprecation('1 occurrences', $trace, ''))); + $this->assertTrue($configuration->isIgnoredDeprecation(new Deprecation('1200 occurrences and more', $trace, ''))); + $this->assertFalse($configuration->isIgnoredDeprecation(new Deprecation('Many occurrences', $trace, ''))); + } + + public function testIgnoreFilePatternInvalid() + { + $filename = $this->createFile(); + $ignorePatterns = [ + '/Test message (.*/', + ]; + file_put_contents($filename, implode("\n", $ignorePatterns)); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('missing closing parenthesis'); + $configuration = Configuration::fromUrlEncodedString('ignoreFile='.urlencode($filename)); + } + + public function testIgnoreFilePatternException() + { + $filename = $this->createFile(); + $ignorePatterns = [ + '/(?:\D+|<\d+>)*[!?]/', + ]; + file_put_contents($filename, implode("\n", $ignorePatterns)); + + $configuration = Configuration::fromUrlEncodedString('ignoreFile='.urlencode($filename)); + $trace = debug_backtrace(); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessageMatches('/[Bb]acktrack limit exhausted/'); + $configuration->isIgnoredDeprecation(new Deprecation('foobar foobar foobar', $trace, '')); + } + + public function testIgnoreFileException() + { + $filename = $this->createFile(); + unlink($filename); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('The ignoreFile "%s" does not exist.', $filename)); + Configuration::fromUrlEncodedString('ignoreFile='.urlencode($filename)); + } + protected function setUp(): void { $this->files = []; diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ignore.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ignore.phpt new file mode 100644 index 0000000000000..44d86203f4777 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ignore.phpt @@ -0,0 +1,80 @@ +--TEST-- +Test DeprecationErrorHandler with an ignoreFile +--FILE-- +testLegacyFoo(); +$foo->testNonLegacyBar(); +?> +--EXPECTF-- +Legacy deprecation notices (1) + +Other deprecation notices (2) + + 1x: root deprecation + + 1x: not ignored bar deprecation + 1x in FooTestCase::testNonLegacyBar diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/quiet.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/quiet.phpt index 91cee8e17fa95..5b6e325106f03 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/quiet.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/quiet.phpt @@ -4,7 +4,7 @@ Test DeprecationErrorHandler with quiet output + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Bridge\PhpUnit\Tests; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php index abd2b1d384f1d..696c0b2e3952e 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php @@ -20,7 +20,7 @@ */ class LazyLoadingValueHolderFactory extends BaseFactory { - private $generator; + private ProxyGeneratorInterface $generator; /** * {@inheritdoc} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php index 1fd3459ae4cb4..003986b8d00fc 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -25,7 +25,7 @@ */ class RuntimeInstantiator implements InstantiatorInterface { - private $factory; + private LazyLoadingValueHolderFactory $factory; public function __construct() { diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index d3f23d393e50f..a5d3707e8ae61 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -26,8 +26,8 @@ class ProxyDumper implements DumperInterface { private string $salt; - private $proxyGenerator; - private $classGenerator; + private LazyLoadingValueHolderGenerator $proxyGenerator; + private BaseGeneratorStrategy $classGenerator; public function __construct(string $salt = '') { diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index 430c4edd1e608..d141f99ba6ee5 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "friendsofphp/proxy-manager-lts": "^1.0.2", "symfony/dependency-injection": "^5.4|^6.0" }, diff --git a/src/Symfony/Bridge/Twig/AppVariable.php b/src/Symfony/Bridge/Twig/AppVariable.php index a6103a25c884a..43d6e3ffc9056 100644 --- a/src/Symfony/Bridge/Twig/AppVariable.php +++ b/src/Symfony/Bridge/Twig/AppVariable.php @@ -25,8 +25,8 @@ */ class AppVariable { - private $tokenStorage; - private $requestStack; + private TokenStorageInterface $tokenStorage; + private RequestStack $requestStack; private string $environment; private bool $debug; @@ -100,7 +100,7 @@ public function getSession(): ?Session } $request = $this->getRequest(); - return $request && $request->hasSession() ? $request->getSession() : null; + return $request?->hasSession() ? $request->getSession() : null; } /** @@ -139,7 +139,7 @@ public function getFlashes(string|array $types = null): array if (null === $session = $this->getSession()) { return []; } - } catch (\RuntimeException $e) { + } catch (\RuntimeException) { return []; } diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 535df0c0897b4..819251bf0d874 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.1 +--- + + * Wrap help messages on form elements in `div` instead of `p` + 5.4 --- diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index e78723be91d1f..d5e64859a6c20 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -36,7 +36,7 @@ #[AsCommand(name: 'debug:twig', description: 'Show a list of twig functions, filters, globals and tests')] class DebugCommand extends Command { - private $twig; + private Environment $twig; private ?string $projectDir; private array $bundlesMetadata; private ?string $twigDefaultPath; @@ -46,7 +46,7 @@ class DebugCommand extends Command */ private array $filesystemLoaders; - private $fileLinkFormatter; + private ?FileLinkFormatter $fileLinkFormatter; public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, FileLinkFormatter $fileLinkFormatter = null) { @@ -294,7 +294,7 @@ private function getLoaderPaths(string $name = null): array } foreach ($namespaces as $namespace) { - $paths = array_map([$this, 'getRelativePath'], $loader->getPaths($namespace)); + $paths = array_map($this->getRelativePath(...), $loader->getPaths($namespace)); if (FilesystemLoader::MAIN_NAMESPACE === $namespace) { $namespace = '(None)'; diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index b7295c87840e2..54f4e233b884d 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -39,14 +39,13 @@ #[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] class LintCommand extends Command { - private $twig; private string $format; - public function __construct(Environment $twig) - { + public function __construct( + private Environment $twig, + private array $namePatterns = ['*.twig'], + ) { parent::__construct(); - - $this->twig = $twig; } protected function configure() @@ -146,7 +145,7 @@ protected function findFiles(string $filename) if (is_file($filename)) { return [$filename]; } elseif (is_dir($filename)) { - return Finder::create()->files()->in($filename)->name('*.twig'); + return Finder::create()->files()->in($filename)->name($this->namePatterns); } throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); @@ -172,16 +171,12 @@ private function validate(string $template, string $file): array private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files) { - switch ($this->format) { - case 'txt': - return $this->displayTxt($output, $io, $files); - case 'json': - return $this->displayJson($output, $files); - case 'github': - return $this->displayTxt($output, $io, $files, true); - default: - throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); - } + return match ($this->format) { + 'txt' => $this->displayTxt($output, $io, $files), + 'json' => $this->displayJson($output, $files), + 'github' => $this->displayTxt($output, $io, $files, true), + default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))), + }; } private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false) @@ -230,9 +225,7 @@ private function renderException(SymfonyStyle $output, string $template, Error $ { $line = $exception->getTemplateLine(); - if ($githubReporter) { - $githubReporter->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line); - } + $githubReporter?->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line); if ($file) { $output->text(sprintf(' ERROR in %s (line %s)', $file, $line)); diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php index ed27f6b89a365..a26a620cfddb0 100644 --- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php +++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php @@ -28,8 +28,8 @@ */ class TwigDataCollector extends DataCollector implements LateDataCollectorInterface { - private $profile; - private $twig; + private Profile $profile; + private ?Environment $twig; private array $computed; public function __construct(Profile $profile, Environment $twig = null) @@ -71,7 +71,7 @@ public function lateCollect() if ($profile->isTemplate()) { try { $template = $this->twig->load($name = $profile->getName()); - } catch (LoaderError $e) { + } catch (LoaderError) { $template = null; } @@ -140,7 +140,7 @@ public function getHtmlCallGraph() public function getProfile() { - return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', 'Twig\Profiler\Profile']]); + return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', Profile::class]]); } private function getComputedData(string $index) diff --git a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php index 50cc9cc91d5b3..4dde73f62c902 100644 --- a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php +++ b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php @@ -25,8 +25,8 @@ */ class TwigErrorRenderer implements ErrorRendererInterface { - private $twig; - private $fallbackErrorRenderer; + private Environment $twig; + private HtmlErrorRenderer $fallbackErrorRenderer; private \Closure|bool $debug; /** @@ -36,7 +36,7 @@ public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorR { $this->twig = $twig; $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); - $this->debug = !\is_callable($debug) || $debug instanceof \Closure ? $debug : \Closure::fromCallable($debug); + $this->debug = \is_bool($debug) ? $debug : $debug(...); } /** diff --git a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php index 694821f7bf6b8..3e12371ba99c3 100644 --- a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php @@ -22,7 +22,7 @@ */ final class AssetExtension extends AbstractExtension { - private $packages; + private Packages $packages; public function __construct(Packages $packages) { @@ -35,8 +35,8 @@ public function __construct(Packages $packages) public function getFunctions(): array { return [ - new TwigFunction('asset', [$this, 'getAssetUrl']), - new TwigFunction('asset_version', [$this, 'getAssetVersion']), + new TwigFunction('asset', $this->getAssetUrl(...)), + new TwigFunction('asset_version', $this->getAssetVersion(...)), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index 62573d9f961e6..7287ec8e749fe 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -39,16 +39,16 @@ public function __construct(string|FileLinkFormatter $fileLinkFormat, string $pr public function getFilters(): array { return [ - new TwigFilter('abbr_class', [$this, 'abbrClass'], ['is_safe' => ['html']]), - new TwigFilter('abbr_method', [$this, 'abbrMethod'], ['is_safe' => ['html']]), - new TwigFilter('format_args', [$this, 'formatArgs'], ['is_safe' => ['html']]), - new TwigFilter('format_args_as_text', [$this, 'formatArgsAsText']), - new TwigFilter('file_excerpt', [$this, 'fileExcerpt'], ['is_safe' => ['html']]), - new TwigFilter('format_file', [$this, 'formatFile'], ['is_safe' => ['html']]), - new TwigFilter('format_file_from_text', [$this, 'formatFileFromText'], ['is_safe' => ['html']]), - new TwigFilter('format_log_message', [$this, 'formatLogMessage'], ['is_safe' => ['html']]), - new TwigFilter('file_link', [$this, 'getFileLink']), - new TwigFilter('file_relative', [$this, 'getFileRelative']), + new TwigFilter('abbr_class', $this->abbrClass(...), ['is_safe' => ['html']]), + new TwigFilter('abbr_method', $this->abbrMethod(...), ['is_safe' => ['html']]), + new TwigFilter('format_args', $this->formatArgs(...), ['is_safe' => ['html']]), + new TwigFilter('format_args_as_text', $this->formatArgsAsText(...)), + new TwigFilter('file_excerpt', $this->fileExcerpt(...), ['is_safe' => ['html']]), + new TwigFilter('format_file', $this->formatFile(...), ['is_safe' => ['html']]), + new TwigFilter('format_file_from_text', $this->formatFileFromText(...), ['is_safe' => ['html']]), + new TwigFilter('format_log_message', $this->formatLogMessage(...), ['is_safe' => ['html']]), + new TwigFilter('file_link', $this->getFileLink(...)), + new TwigFilter('file_relative', $this->getFileRelative(...)), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php b/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php index c3d5da6470c25..216d9c92f10ed 100644 --- a/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php +++ b/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php @@ -19,7 +19,7 @@ */ final class CsrfRuntime { - private $csrfTokenManager; + private CsrfTokenManagerInterface $csrfTokenManager; public function __construct(CsrfTokenManagerInterface $csrfTokenManager) { diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php index 46ad8eaf679c2..910fece83b9bb 100644 --- a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php @@ -26,8 +26,8 @@ */ final class DumpExtension extends AbstractExtension { - private $cloner; - private $dumper; + private ClonerInterface $cloner; + private ?HtmlDumper $dumper; public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null) { @@ -41,7 +41,7 @@ public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null) public function getFunctions(): array { return [ - new TwigFunction('dump', [$this, 'dump'], ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]), + new TwigFunction('dump', $this->dump(...), ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php index 8d2a35c99f682..a30499620ea0f 100644 --- a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php @@ -28,7 +28,7 @@ final class ExpressionExtension extends AbstractExtension public function getFunctions(): array { return [ - new TwigFunction('expression', [$this, 'createExpression']), + new TwigFunction('expression', $this->createExpression(...)), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index f7d82e042138d..f3484ca7715d1 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -11,10 +11,13 @@ namespace Symfony\Bridge\Twig\Extension; +use Symfony\Bridge\Twig\Node\RenderBlockNode; +use Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode; use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormView; use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Extension\AbstractExtension; @@ -30,7 +33,7 @@ */ final class FormExtension extends AbstractExtension { - private $translator; + private ?TranslatorInterface $translator; public function __construct(TranslatorInterface $translator = null) { @@ -54,23 +57,23 @@ public function getTokenParsers(): array public function getFunctions(): array { return [ - new TwigFunction('form_widget', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_errors', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_label', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_help', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_row', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_rest', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_start', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('form_end', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]), - new TwigFunction('csrf_token', ['Symfony\Component\Form\FormRenderer', 'renderCsrfToken']), + new TwigFunction('form_widget', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_errors', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_label', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_help', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_row', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_rest', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_start', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('form_end', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]), + new TwigFunction('csrf_token', [FormRenderer::class, 'renderCsrfToken']), new TwigFunction('form_parent', 'Symfony\Bridge\Twig\Extension\twig_get_form_parent'), - new TwigFunction('field_name', [$this, 'getFieldName']), - new TwigFunction('field_value', [$this, 'getFieldValue']), - new TwigFunction('field_label', [$this, 'getFieldLabel']), - new TwigFunction('field_help', [$this, 'getFieldHelp']), - new TwigFunction('field_errors', [$this, 'getFieldErrors']), - new TwigFunction('field_choices', [$this, 'getFieldChoices']), + new TwigFunction('field_name', $this->getFieldName(...)), + new TwigFunction('field_value', $this->getFieldValue(...)), + new TwigFunction('field_label', $this->getFieldLabel(...)), + new TwigFunction('field_help', $this->getFieldHelp(...)), + new TwigFunction('field_errors', $this->getFieldErrors(...)), + new TwigFunction('field_choices', $this->getFieldChoices(...)), ]; } @@ -80,8 +83,8 @@ public function getFunctions(): array public function getFilters(): array { return [ - new TwigFilter('humanize', ['Symfony\Component\Form\FormRenderer', 'humanize']), - new TwigFilter('form_encode_currency', ['Symfony\Component\Form\FormRenderer', 'encodeCurrency'], ['is_safe' => ['html'], 'needs_environment' => true]), + new TwigFilter('humanize', [FormRenderer::class, 'humanize']), + new TwigFilter('form_encode_currency', [FormRenderer::class, 'encodeCurrency'], ['is_safe' => ['html'], 'needs_environment' => true]), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php index a9ee05c4d0093..104bd3231f773 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php @@ -23,7 +23,7 @@ */ final class HttpFoundationExtension extends AbstractExtension { - private $urlHelper; + private UrlHelper $urlHelper; public function __construct(UrlHelper $urlHelper) { @@ -36,8 +36,8 @@ public function __construct(UrlHelper $urlHelper) public function getFunctions(): array { return [ - new TwigFunction('absolute_url', [$this, 'generateAbsoluteUrl']), - new TwigFunction('relative_path', [$this, 'generateRelativePath']), + new TwigFunction('absolute_url', $this->generateAbsoluteUrl(...)), + new TwigFunction('relative_path', $this->generateRelativePath(...)), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php index 7c86d7dd40add..b059bf1aae4c3 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php @@ -22,8 +22,8 @@ */ final class HttpKernelRuntime { - private $handler; - private $fragmentUriGenerator; + private FragmentHandler $handler; + private ?FragmentUriGeneratorInterface $fragmentUriGenerator; public function __construct(FragmentHandler $handler, FragmentUriGeneratorInterface $fragmentUriGenerator = null) { diff --git a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php index 071b9ff247f1d..3ac7582b8fbeb 100644 --- a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php @@ -22,7 +22,7 @@ */ final class LogoutUrlExtension extends AbstractExtension { - private $generator; + private LogoutUrlGenerator $generator; public function __construct(LogoutUrlGenerator $generator) { @@ -35,8 +35,8 @@ public function __construct(LogoutUrlGenerator $generator) public function getFunctions(): array { return [ - new TwigFunction('logout_url', [$this, 'getLogoutUrl']), - new TwigFunction('logout_path', [$this, 'getLogoutPath']), + new TwigFunction('logout_url', $this->getLogoutUrl(...)), + new TwigFunction('logout_path', $this->getLogoutPath(...)), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php index cba3ab8d46329..f63aa41cf2738 100644 --- a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php @@ -21,7 +21,7 @@ */ final class ProfilerExtension extends BaseProfilerExtension { - private $stopwatch; + private ?Stopwatch $stopwatch; /** * @var \SplObjectStorage diff --git a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php index 800c22f6d4c2c..3b3a2ac58e27b 100644 --- a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php @@ -25,7 +25,7 @@ */ final class RoutingExtension extends AbstractExtension { - private $generator; + private UrlGeneratorInterface $generator; public function __construct(UrlGeneratorInterface $generator) { @@ -38,8 +38,8 @@ public function __construct(UrlGeneratorInterface $generator) public function getFunctions(): array { return [ - new TwigFunction('url', [$this, 'getUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), - new TwigFunction('path', [$this, 'getPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + new TwigFunction('url', $this->getUrl(...), ['is_safe_callback' => $this->isUrlGenerationSafe(...)]), + new TwigFunction('path', $this->getPath(...), ['is_safe_callback' => $this->isUrlGenerationSafe(...)]), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php index aedeefdca9d52..dd1c57fb3b36e 100644 --- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php @@ -25,8 +25,8 @@ */ final class SecurityExtension extends AbstractExtension { - private $securityChecker; - private $impersonateUrlGenerator; + private ?AuthorizationCheckerInterface $securityChecker; + private ?ImpersonateUrlGenerator $impersonateUrlGenerator; public function __construct(AuthorizationCheckerInterface $securityChecker = null, ImpersonateUrlGenerator $impersonateUrlGenerator = null) { @@ -46,7 +46,7 @@ public function isGranted(mixed $role, mixed $object = null, string $field = nul try { return $this->securityChecker->isGranted($role, $object); - } catch (AuthenticationCredentialsNotFoundException $e) { + } catch (AuthenticationCredentialsNotFoundException) { return false; } } @@ -75,9 +75,9 @@ public function getImpersonateExitPath(string $exitTo = null): string public function getFunctions(): array { return [ - new TwigFunction('is_granted', [$this, 'isGranted']), - new TwigFunction('impersonation_exit_url', [$this, 'getImpersonateExitUrl']), - new TwigFunction('impersonation_exit_path', [$this, 'getImpersonateExitPath']), + new TwigFunction('is_granted', $this->isGranted(...)), + new TwigFunction('impersonation_exit_url', $this->getImpersonateExitUrl(...)), + new TwigFunction('impersonation_exit_path', $this->getImpersonateExitPath(...)), ]; } } diff --git a/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php b/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php index dbffa31c2741b..b48be3aae0163 100644 --- a/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php +++ b/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php @@ -19,7 +19,7 @@ */ final class SerializerRuntime implements RuntimeExtensionInterface { - private $serializer; + private SerializerInterface $serializer; public function __construct(SerializerInterface $serializer) { diff --git a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php index 635216f23203b..972cd1acda44c 100644 --- a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php @@ -23,7 +23,7 @@ */ final class StopwatchExtension extends AbstractExtension { - private $stopwatch; + private ?Stopwatch $stopwatch; private bool $enabled; public function __construct(Stopwatch $stopwatch = null, bool $enabled = true) diff --git a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php index fa5c66fd43c63..cbb7394e7d848 100644 --- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php @@ -34,8 +34,8 @@ class_exists(TranslatorTrait::class); */ final class TranslationExtension extends AbstractExtension { - private $translator; - private $translationNodeVisitor; + private ?TranslatorInterface $translator; + private ?TranslationNodeVisitor $translationNodeVisitor; public function __construct(TranslatorInterface $translator = null, TranslationNodeVisitor $translationNodeVisitor = null) { @@ -64,7 +64,7 @@ public function getTranslator(): TranslatorInterface public function getFunctions(): array { return [ - new TwigFunction('t', [$this, 'createTranslatable']), + new TwigFunction('t', $this->createTranslatable(...)), ]; } @@ -74,7 +74,7 @@ public function getFunctions(): array public function getFilters(): array { return [ - new TwigFilter('trans', [$this, 'trans']), + new TwigFilter('trans', $this->trans(...)), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php index 652a75762c63b..fcc34ecb5aad6 100644 --- a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php @@ -24,7 +24,7 @@ */ final class WebLinkExtension extends AbstractExtension { - private $requestStack; + private RequestStack $requestStack; public function __construct(RequestStack $requestStack) { @@ -37,12 +37,12 @@ public function __construct(RequestStack $requestStack) public function getFunctions(): array { return [ - new TwigFunction('link', [$this, 'link']), - new TwigFunction('preload', [$this, 'preload']), - new TwigFunction('dns_prefetch', [$this, 'dnsPrefetch']), - new TwigFunction('preconnect', [$this, 'preconnect']), - new TwigFunction('prefetch', [$this, 'prefetch']), - new TwigFunction('prerender', [$this, 'prerender']), + new TwigFunction('link', $this->link(...)), + new TwigFunction('preload', $this->preload(...)), + new TwigFunction('dns_prefetch', $this->dnsPrefetch(...)), + new TwigFunction('preconnect', $this->preconnect(...)), + new TwigFunction('prefetch', $this->prefetch(...)), + new TwigFunction('prerender', $this->prerender(...)), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php index 3ee1ac3d6b6b2..895faf457f648 100644 --- a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @@ -25,7 +25,7 @@ */ final class WorkflowExtension extends AbstractExtension { - private $workflowRegistry; + private Registry $workflowRegistry; public function __construct(Registry $workflowRegistry) { @@ -38,13 +38,13 @@ public function __construct(Registry $workflowRegistry) public function getFunctions(): array { return [ - new TwigFunction('workflow_can', [$this, 'canTransition']), - new TwigFunction('workflow_transitions', [$this, 'getEnabledTransitions']), - new TwigFunction('workflow_transition', [$this, 'getEnabledTransition']), - new TwigFunction('workflow_has_marked_place', [$this, 'hasMarkedPlace']), - new TwigFunction('workflow_marked_places', [$this, 'getMarkedPlaces']), - new TwigFunction('workflow_metadata', [$this, 'getMetadata']), - new TwigFunction('workflow_transition_blockers', [$this, 'buildTransitionBlockerList']), + new TwigFunction('workflow_can', $this->canTransition(...)), + new TwigFunction('workflow_transitions', $this->getEnabledTransitions(...)), + new TwigFunction('workflow_transition', $this->getEnabledTransition(...)), + new TwigFunction('workflow_has_marked_place', $this->hasMarkedPlace(...)), + new TwigFunction('workflow_marked_places', $this->getMarkedPlaces(...)), + new TwigFunction('workflow_metadata', $this->getMetadata(...)), + new TwigFunction('workflow_transition_blockers', $this->buildTransitionBlockerList(...)), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php index 919834e24a5d6..2d0595b7e9bca 100644 --- a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php @@ -28,8 +28,8 @@ final class YamlExtension extends AbstractExtension public function getFilters(): array { return [ - new TwigFilter('yaml_encode', [$this, 'encode']), - new TwigFilter('yaml_dump', [$this, 'dump']), + new TwigFilter('yaml_encode', $this->encode(...)), + new TwigFilter('yaml_dump', $this->dump(...)), ]; } diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php index 693ee81239924..6f408ebb584ad 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php @@ -21,8 +21,8 @@ */ class TwigRendererEngine extends AbstractRendererEngine { - private $environment; - private $template; + private Environment $environment; + private Template $template; public function __construct(array $defaultThemes, Environment $environment) { diff --git a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php index e50efa06f6efe..85e0ba93d42c9 100644 --- a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php +++ b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php @@ -22,9 +22,9 @@ */ final class BodyRenderer implements BodyRendererInterface { - private $twig; + private Environment $twig; private array $context; - private $converter; + private HtmlConverter $converter; public function __construct(Environment $twig, array $context = []) { @@ -85,7 +85,7 @@ private function getFingerPrint(TemplatedEmail $message): string $payload = [$messageContext, $message->getTextTemplate(), $message->getHtmlTemplate()]; try { $serialized = serialize($payload); - } catch (\Exception $e) { + } 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); diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index 8e71df1b0e206..60a29304c9b20 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -192,17 +192,12 @@ public function getPreparedHeaders(): Headers private function determinePriority(string $importance): int { - switch ($importance) { - case self::IMPORTANCE_URGENT: - return self::PRIORITY_HIGHEST; - case self::IMPORTANCE_HIGH: - return self::PRIORITY_HIGH; - case self::IMPORTANCE_MEDIUM: - return self::PRIORITY_NORMAL; - case self::IMPORTANCE_LOW: - default: - return self::PRIORITY_LOW; - } + return match ($importance) { + self::IMPORTANCE_URGENT => self::PRIORITY_HIGHEST, + self::IMPORTANCE_HIGH => self::PRIORITY_HIGH, + self::IMPORTANCE_MEDIUM => self::PRIORITY_NORMAL, + default => self::PRIORITY_LOW, + }; } private function getExceptionAsString(\Throwable|FlattenException $exception): string diff --git a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php index b567667883ef3..e0b3bef29308f 100644 --- a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php @@ -21,8 +21,8 @@ */ final class WrappedTemplatedEmail { - private $twig; - private $message; + private Environment $twig; + private TemplatedEmail $message; public function __construct(Environment $twig, TemplatedEmail $message) { diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 213365ed9f1ef..61c8b5ff52083 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -30,7 +30,7 @@ */ final class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor { - private $scope; + private Scope $scope; public function __construct() { 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 94f87dc165ec6..4769585c22772 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 @@ -325,7 +325,7 @@ {% block form_help -%} {%- 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 -}} @@ -339,7 +339,7 @@ {{- help|trans(help_translation_parameters, translation_domain)|raw -}} {%- endif -%} {%- endif -%} -

+
{%- endif -%} {%- endblock form_help %} diff --git a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php index 462f9f7874879..6ba4ca77e51c4 100644 --- a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php +++ b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.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\Twig\Tests; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php index 9fe231bfa1198..1c2a40c221671 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5HorizontalLayoutTest.php @@ -245,7 +245,7 @@ public function testCheckboxRowWithHelp() ./div[@class="col-sm-2" or @class="col-sm-10"] /following-sibling::div[@class="col-sm-2" or @class="col-sm-10"] [ - ./p + ./div [@class="form-text mb-0 help-text"] [.="[trans]really helpful text[/trans]"] ] @@ -266,7 +266,7 @@ public function testRadioRowWithHelp() ./div[@class="col-sm-2" or @class="col-sm-10"] /following-sibling::div[@class="col-sm-2" or @class="col-sm-10"] [ - ./p + ./div [@class="form-text mb-0 help-text"] [.="[trans]really helpful text[/trans]"] ] diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php index ff35789a564cd..1403372bc5599 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php @@ -198,7 +198,7 @@ public function testHelp() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] [.="[trans]Help text test![/trans]"] @@ -218,7 +218,7 @@ public function testHelpAttr() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="class-test form-text mb-0 help-text"] [.="[trans]Help text test![/trans]"] @@ -236,7 +236,7 @@ public function testHelpHtmlDefaultIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] [.="[trans]Help text test![/trans]"] @@ -244,7 +244,7 @@ public function testHelpHtmlDefaultIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] /b @@ -264,7 +264,7 @@ public function testHelpHtmlIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] [.="[trans]Help text test![/trans]"] @@ -272,7 +272,7 @@ public function testHelpHtmlIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] /b @@ -290,7 +290,7 @@ public function testHelpHtmlIsTrue() $html = $this->renderHelp($form->createView()); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] [.="[trans]Help text test![/trans]"] @@ -298,7 +298,7 @@ public function testHelpHtmlIsTrue() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="form-text mb-0 help-text"] /b @@ -377,7 +377,7 @@ public function testCheckboxRowWithHelp() [@for="name"] [.="[trans]foo[/trans]"] ] - /following-sibling::p + /following-sibling::div [@class="form-text mb-0 help-text"] [.="[trans]really helpful text[/trans]"] ] @@ -871,7 +871,7 @@ public function testRadioRowWithHelp() '/div [@class="mb-3"] [ - ./p + ./div [@class="form-text mb-0 help-text"] [.="[trans]really helpful text[/trans]"] ] diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index e7991240f03f5..cae1a1c6e6b4d 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -203,7 +203,7 @@ public function testHelpAttr() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="class-test help-text"] [.="[trans]Help text test![/trans]"] @@ -221,7 +221,7 @@ public function testHelpHtmlDefaultIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -229,7 +229,7 @@ public function testHelpHtmlDefaultIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b @@ -249,7 +249,7 @@ public function testHelpHtmlIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -257,7 +257,7 @@ public function testHelpHtmlIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b @@ -277,7 +277,7 @@ public function testHelpHtmlIsTrue() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -285,7 +285,7 @@ public function testHelpHtmlIsTrue() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php index 20b465418daea..7b75be234da3a 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php @@ -89,7 +89,7 @@ public function testHelpAttr() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="class-test help-text"] [.="[trans]Help text test![/trans]"] @@ -107,7 +107,7 @@ public function testHelpHtmlDefaultIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -115,7 +115,7 @@ public function testHelpHtmlDefaultIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b @@ -135,7 +135,7 @@ public function testHelpHtmlIsFalse() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -143,7 +143,7 @@ public function testHelpHtmlIsFalse() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b @@ -163,7 +163,7 @@ public function testHelpHtmlIsTrue() $html = $this->renderHelp($view); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] [.="[trans]Help text test![/trans]"] @@ -171,7 +171,7 @@ public function testHelpHtmlIsTrue() ); $this->assertMatchesXpath($html, - '/p + '/div [@id="name_help"] [@class="help-text"] /b diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php index 6eed243690387..b0e064e392426 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.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\Twig\Tests\Mime; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php index 2da04921446eb..f0fc64466a104 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.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\Twig\Tests\Mime; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php index 4a8b4d19c066e..c93bd718bc903 100644 --- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php @@ -41,7 +41,6 @@ public function testExtract($template, $messages) $catalogue = new MessageCatalogue('en'); $m = new \ReflectionMethod($extractor, 'extractTemplate'); - $m->setAccessible(true); $m->invoke($extractor, $template, $catalogue); if (0 === \count($messages)) { diff --git a/src/Symfony/Bridge/Twig/TokenParser/StopwatchTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/StopwatchTokenParser.php index f0b3ac2e9240e..b332485d02577 100644 --- a/src/Symfony/Bridge/Twig/TokenParser/StopwatchTokenParser.php +++ b/src/Symfony/Bridge/Twig/TokenParser/StopwatchTokenParser.php @@ -42,7 +42,7 @@ public function parse(Token $token): Node $stream->expect(Token::BLOCK_END_TYPE); // {% endstopwatch %} - $body = $this->parser->subparse([$this, 'decideStopwatchEnd'], true); + $body = $this->parser->subparse($this->decideStopwatchEnd(...), true); $stream->expect(Token::BLOCK_END_TYPE); if ($this->stopwatchIsAvailable) { diff --git a/src/Symfony/Bridge/Twig/TokenParser/TransTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/TransTokenParser.php index ffe8828590852..b440931bba794 100644 --- a/src/Symfony/Bridge/Twig/TokenParser/TransTokenParser.php +++ b/src/Symfony/Bridge/Twig/TokenParser/TransTokenParser.php @@ -69,7 +69,7 @@ public function parse(Token $token): Node // {% trans %}message{% endtrans %} $stream->expect(Token::BLOCK_END_TYPE); - $body = $this->parser->subparse([$this, 'decideTransFork'], true); + $body = $this->parser->subparse($this->decideTransFork(...), true); if (!$body instanceof TextNode && !$body instanceof AbstractExpression) { throw new SyntaxError('A message inside a trans tag must be a simple text.', $body->getTemplateLine(), $stream->getSourceContext()); diff --git a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php index 1ec9a13d2d5cf..86ef269328530 100644 --- a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php +++ b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Twig\Translation; +use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Component\Finder\Finder; use Symfony\Component\Translation\Extractor\AbstractFileExtractor; use Symfony\Component\Translation\Extractor\ExtractorInterface; @@ -37,7 +38,7 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface */ private string $prefix = ''; - private $twig; + private Environment $twig; public function __construct(Environment $twig) { @@ -52,7 +53,7 @@ public function extract($resource, MessageCatalogue $catalogue) foreach ($this->extractFiles($resource) as $file) { try { $this->extractTemplate(file_get_contents($file->getPathname()), $catalogue); - } catch (Error $e) { + } catch (Error) { // ignore errors, these should be fixed by using the linter } } @@ -68,7 +69,7 @@ public function setPrefix(string $prefix) protected function extractTemplate(string $template, MessageCatalogue $catalogue) { - $visitor = $this->twig->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->getTranslationNodeVisitor(); + $visitor = $this->twig->getExtension(TranslationExtension::class)->getTranslationNodeVisitor(); $visitor->enable(); $this->twig->parse($this->twig->tokenize(new Source($template, ''))); diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 0765818346b6d..46cea3b115b4d 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/translation-contracts": "^1.1|^2|^3", "twig/twig": "^2.13|^3.0.4" }, @@ -27,7 +27,7 @@ "symfony/asset": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", + "symfony/form": "^6.1", "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^5.4|^6.0", "symfony/intl": "^5.4|^6.0", @@ -55,7 +55,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/console": "<5.4", - "symfony/form": "<5.4", + "symfony/form": "<6.1", "symfony/http-foundation": "<5.4", "symfony/http-kernel": "<5.4", "symfony/translation": "<5.4", diff --git a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig index 7f078b93ac34c..ee009cc6fa508 100644 --- a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig +++ b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig @@ -3,7 +3,7 @@ {% block toolbar %} {% if collector.dumpsCount %} {% set icon %} - {{ include('@Debug/Profiler/icon.svg') }} + {{ source('@Debug/Profiler/icon.svg') }} {{ collector.dumpsCount }} {% endset %} @@ -35,7 +35,7 @@ {% block menu %} - {{ include('@Debug/Profiler/icon.svg') }} + {{ source('@Debug/Profiler/icon.svg') }} Debug {% endblock %} diff --git a/src/Symfony/Bundle/DebugBundle/composer.json b/src/Symfony/Bundle/DebugBundle/composer.json index 6dc3f8c126b0a..619db7d66feed 100644 --- a/src/Symfony/Bundle/DebugBundle/composer.json +++ b/src/Symfony/Bundle/DebugBundle/composer.json @@ -16,15 +16,15 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "ext-xml": "*", + "symfony/dependency-injection": "^5.4|^6.0", "symfony/http-kernel": "^5.4|^6.0", "symfony/twig-bridge": "^5.4|^6.0", "symfony/var-dumper": "^5.4|^6.0" }, "require-dev": { "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", "symfony/web-profiler-bundle": "^5.4|^6.0" }, "conflict": { diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 862a1ae089c7c..f13c4790988d4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,19 @@ CHANGELOG ========= +6.1 +--- + + * Add support for configuring semaphores + * Environment variable `SYMFONY_IDE` is read by default when `framework.ide` config is not set + * Load PHP configuration files by default in the `MicroKernelTrait` + * Add `cache:pool:invalidate-tags` command + * Add `xliff` support in addition to `xlf` for `XliffFileDumper` + * Deprecate the `reset_on_message` config option. It can be set to `true` only and does nothing now + * Add `trust_x_sendfile_type_header` option + * Add support for first-class callable route controller in `MicroKernelTrait` + * Add tag `routing.condition_service` to autoconfigure routing condition services + 6.0 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php index 097a356008a5b..f72df9e6542bc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AbstractPhpFileCacheWarmer.php @@ -78,7 +78,7 @@ final protected function ignoreAutoloadException(string $class, \Exception $exce { try { ClassExistenceResource::throwOnRequiredClass($class, $exception); - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php index e10471617b2e3..2fd5f661d2dee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php @@ -25,7 +25,7 @@ */ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer { - private $annotationReader; + private Reader $annotationReader; private ?string $excludeRegexp; private bool $debug; @@ -85,7 +85,7 @@ private function readAllComponents(Reader $reader, string $class) try { $reader->getClassAnnotations($reflectionClass); - } catch (AnnotationException $e) { + } catch (AnnotationException) { /* * Ignore any AnnotationException to not break the cache warming process if an Annotation is badly * configured or could not be found / read / etc. @@ -99,14 +99,14 @@ private function readAllComponents(Reader $reader, string $class) foreach ($reflectionClass->getMethods() as $reflectionMethod) { try { $reader->getMethodAnnotations($reflectionMethod); - } catch (AnnotationException $e) { + } catch (AnnotationException) { } } foreach ($reflectionClass->getProperties() as $reflectionProperty) { try { $reader->getPropertyAnnotations($reflectionProperty); - } catch (AnnotationException $e) { + } catch (AnnotationException) { } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php index cd184cf64762c..41041aedaed99 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/CachePoolClearerCacheWarmer.php @@ -25,7 +25,7 @@ */ final class CachePoolClearerCacheWarmer implements CacheWarmerInterface { - private $poolClearer; + private Psr6CacheClearer $poolClearer; private array $pools; /** diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php index c0d96c5ea7b74..95f62d9202203 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php @@ -28,8 +28,8 @@ */ class ConfigBuilderCacheWarmer implements CacheWarmerInterface { - private $kernel; - private $logger; + private KernelInterface $kernel; + private ?LoggerInterface $logger; public function __construct(KernelInterface $kernel, LoggerInterface $logger = null) { @@ -55,9 +55,7 @@ public function warmUp(string $cacheDir): array try { $this->dumpExtension($extension, $generator); } catch (\Exception $e) { - if ($this->logger) { - $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' => \get_class($extension)]); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php index 6cdf176bb33bb..21dd3a2728845 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php @@ -26,7 +26,7 @@ */ class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface { - private $container; + private ContainerInterface $container; public function __construct(ContainerInterface $container) { diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php index 4aae3ba96eaf5..ccc1402e2ddfe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php @@ -54,7 +54,7 @@ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter): bool foreach ($loader->getMappedClasses() as $mappedClass) { try { $metadataFactory->getMetadataFor($mappedClass); - } catch (AnnotationException $e) { + } catch (AnnotationException) { // ignore failing annotations } catch (\Exception $e) { $this->ignoreAutoloadException($mappedClass, $e); diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php index 78820decd811a..8cddae7e308ce 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php @@ -24,8 +24,8 @@ */ class TranslationsCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface { - private $container; - private $translator; + private ContainerInterface $container; + private TranslatorInterface $translator; public function __construct(ContainerInterface $container) { diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php index 6b2c558be96e5..04025a49bba66 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php @@ -28,7 +28,7 @@ */ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer { - private $validatorBuilder; + private ValidatorBuilder $validatorBuilder; /** * @param string $phpArrayFile The PHP file where metadata are cached @@ -57,7 +57,7 @@ protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter): bool if ($metadataFactory->hasMetadataFor($mappedClass)) { $metadataFactory->getMetadataFor($mappedClass); } - } catch (AnnotationException $e) { + } catch (AnnotationException) { // ignore failing annotations } catch (\Exception $e) { $this->ignoreAutoloadException($mappedClass, $e); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index a46a0005dca40..aaad074daf246 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -41,7 +41,7 @@ class AssetsInstallCommand extends Command public const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; public const METHOD_RELATIVE_SYMLINK = 'relative symlink'; - private $filesystem; + private Filesystem $filesystem; private string $projectDir; public function __construct(Filesystem $filesystem, string $projectDir) @@ -202,7 +202,7 @@ private function relativeSymlinkWithFallback(string $originDir, string $targetDi try { $this->symlink($originDir, $targetDir, true); $method = self::METHOD_RELATIVE_SYMLINK; - } catch (IOException $e) { + } catch (IOException) { $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); } @@ -219,7 +219,7 @@ private function absoluteSymlinkWithFallback(string $originDir, string $targetDi try { $this->symlink($originDir, $targetDir); $method = self::METHOD_ABSOLUTE_SYMLINK; - } catch (IOException $e) { + } catch (IOException) { // fall back to copy $method = $this->hardCopy($originDir, $targetDir); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index d9b2730506020..b7d95cfec87f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -37,8 +37,8 @@ #[AsCommand(name: 'cache:clear', description: 'Clear the cache')] class CacheClearCommand extends Command { - private $cacheClearer; - private $filesystem; + private CacheClearerInterface $cacheClearer; + private Filesystem $filesystem; public function __construct(CacheClearerInterface $cacheClearer, Filesystem $filesystem = null) { @@ -90,7 +90,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $useBuildDir = $realBuildDir !== $realCacheDir; - $oldBuildDir = substr($realBuildDir, 0, -1).('~' === substr($realBuildDir, -1) ? '+' : '~'); + $oldBuildDir = substr($realBuildDir, 0, -1).(str_ends_with($realBuildDir, '~') ? '+' : '~'); if ($useBuildDir) { $fs->remove($oldBuildDir); @@ -120,7 +120,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // the warmup cache dir name must have the same length as the real one // to avoid the many problems in serialized resources files - $warmupDir = substr($realBuildDir, 0, -1).('_' === substr($realBuildDir, -1) ? '-' : '_'); + $warmupDir = substr($realBuildDir, 0, -1).(str_ends_with($realBuildDir, '_') ? '-' : '_'); if ($output->isVerbose() && $fs->exists($warmupDir)) { $io->comment('Clearing outdated warmup directory...'); @@ -217,7 +217,7 @@ private function isNfs(string $dir): bool } } foreach ($mounts as $mount) { - if (0 === strpos($dir, $mount)) { + if (str_starts_with($dir, $mount)) { return true; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php index 1861645054380..65da5d7709487 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php @@ -31,7 +31,7 @@ #[AsCommand(name: 'cache:pool:clear', description: 'Clear cache pools')] final class CachePoolClearCommand extends Command { - private $poolClearer; + private Psr6CacheClearer $poolClearer; private ?array $poolNames; /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php index 16aa16c0004ce..546bd631d492c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php @@ -29,7 +29,7 @@ #[AsCommand(name: 'cache:pool:delete', description: 'Delete an item from a cache pool')] final class CachePoolDeleteCommand extends Command { - private $poolClearer; + private Psr6CacheClearer $poolClearer; private ?array $poolNames; /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php new file mode 100644 index 0000000000000..8ad84bfa69503 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolInvalidateTagsCommand.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +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\Exception\ServiceNotFoundException; +use Symfony\Contracts\Cache\TagAwareCacheInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; + +/** + * @author Kevin Bond + */ +#[AsCommand(name: 'cache:pool:invalidate-tags', description: 'Invalidate cache tags for all or a specific pool')] +final class CachePoolInvalidateTagsCommand extends Command +{ + private ServiceProviderInterface $pools; + private array $poolNames; + + public function __construct(ServiceProviderInterface $pools) + { + parent::__construct(); + + $this->pools = $pools; + $this->poolNames = array_keys($pools->getProvidedServices()); + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->addArgument('tags', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The tags to invalidate') + ->addOption('pool', 'p', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The pools to invalidate on') + ->setHelp(<<<'EOF' + The %command.name% command invalidates tags from taggable pools. By default, all pools + have the passed tags invalidated. Pass --pool=my_pool to invalidate tags on a specific pool. + + php %command.full_name% tag1 tag2 + php %command.full_name% tag1 tag2 --pool=cache2 --pool=cache1 + EOF) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $pools = $input->getOption('pool') ?: $this->poolNames; + $tags = $input->getArgument('tags'); + $tagList = implode(', ', $tags); + $errors = false; + + foreach ($pools as $name) { + $io->comment(sprintf('Invalidating tag(s): %s from pool %s.', $tagList, $name)); + + try { + $pool = $this->pools->get($name); + } catch (ServiceNotFoundException) { + $io->error(sprintf('Pool "%s" not found.', $name)); + $errors = true; + + continue; + } + + if (!$pool instanceof TagAwareCacheInterface) { + $io->error(sprintf('Pool "%s" is not taggable.', $name)); + $errors = true; + + continue; + } + + if (!$pool->invalidateTags($tags)) { + $io->error(sprintf('Cache tag(s) "%s" could not be invalidated for pool "%s".', $tagList, $name)); + $errors = true; + } + } + + if ($errors) { + $io->error('Done but with errors.'); + + return self::FAILURE; + } + + $io->success('Successfully invalidated cache tags.'); + + return self::SUCCESS; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('pool')) { + $suggestions->suggestValues($this->poolNames); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php index 7a8e0cc555246..36c2f76e7e3cf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php @@ -30,7 +30,7 @@ #[AsCommand(name: 'cache:warmup', description: 'Warm up an empty cache')] class CacheWarmupCommand extends Command { - private $cacheWarmer; + private CacheWarmerAggregate $cacheWarmer; public function __construct(CacheWarmerAggregate $cacheWarmer) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index fd01616394763..b2557be7b0e2d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -127,7 +127,6 @@ private function compileContainer(): ContainerBuilder $kernel->boot(); $method = new \ReflectionMethod($kernel, 'buildContainer'); - $method->setAccessible(true); $container = $method->invoke($kernel); $container->getCompiler()->compile($container); @@ -196,7 +195,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti $config = $this->getConfig($this->findExtension($name), $this->compileContainer()); $paths = array_keys(self::buildPathsCompletion($config)); $suggestions->suggestValues($paths); - } catch (LogicException $e) { + } catch (LogicException) { } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index b579998231215..c5c2da4a4407c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -132,7 +132,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $options = ['env-vars' => true, 'name' => $envVar]; } elseif ($input->getOption('types')) { $options = []; - $options['filter'] = [$this, 'filterToServiceTypes']; + $options['filter'] = $this->filterToServiceTypes(...); } elseif ($input->getOption('parameters')) { $parameters = []; foreach ($object->getParameterBag()->all() as $k => $v) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php index cfd965db98e55..5e3e891ee845f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php @@ -31,7 +31,7 @@ #[AsCommand(name: 'lint:container', description: 'Ensure that arguments injected into services match type declarations')] final class ContainerLintCommand extends Command { - private $containerBuilder; + private ContainerBuilder $containerBuilder; /** * {@inheritdoc} @@ -104,7 +104,6 @@ private function getContainerBuilder(): ContainerBuilder (new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernelContainer->getParameter('debug.container.dump')); $refl = new \ReflectionProperty($parameterBag, 'resolved'); - $refl->setAccessible(true); $refl->setValue($parameterBag, true); $skippedIds = []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php index 92a76bdcf77e4..ecd507127c00c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php @@ -34,7 +34,7 @@ class DebugAutowiringCommand extends ContainerDebugCommand { private bool $supportsHref; - private $fileLinkFormatter; + private ?FileLinkFormatter $fileLinkFormatter; public function __construct(string $name = null, FileLinkFormatter $fileLinkFormatter = null) { @@ -78,7 +78,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $builder = $this->getContainerBuilder($this->getApplication()->getKernel()); $serviceIds = $builder->getServiceIds(); - $serviceIds = array_filter($serviceIds, [$this, 'filterToServiceTypes']); + $serviceIds = array_filter($serviceIds, $this->filterToServiceTypes(...)); if ($search = $input->getArgument('search')) { $searchNormalized = preg_replace('/[^a-zA-Z0-9\x7f-\xff $]++/', '', $search); @@ -169,7 +169,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti if ($input->mustSuggestArgumentValuesFor('search')) { $builder = $this->getContainerBuilder($this->getApplication()->getKernel()); - $suggestions->suggestValues(array_filter($builder->getServiceIds(), [$this, 'filterToServiceTypes'])); + $suggestions->suggestValues(array_filter($builder->getServiceIds(), $this->filterToServiceTypes(...))); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php index 0a8cdaf04aac7..a8bb8303071db 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php @@ -37,7 +37,7 @@ class EventDispatcherDebugCommand extends Command { private const DEFAULT_DISPATCHER = 'event_dispatcher'; - private $dispatchers; + private ContainerInterface $dispatchers; public function __construct(ContainerInterface $dispatchers) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index cf2c93b05e781..7d2d6e9340304 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -39,8 +39,8 @@ class RouterDebugCommand extends Command { use BuildDebugContainerTrait; - private $router; - private $fileLinkFormatter; + private RouterInterface $router; + private ?FileLinkFormatter $fileLinkFormatter; public function __construct(RouterInterface $router, FileLinkFormatter $fileLinkFormatter = null) { @@ -91,7 +91,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($name) { - if (!($route = $routes->get($name)) && $matchingRoutes = $this->findRouteNameContaining($name, $routes)) { + $route = $routes->get($name); + $matchingRoutes = $this->findRouteNameContaining($name, $routes); + + if (!$input->isInteractive() && !$route && \count($matchingRoutes) > 1) { + $helper->describe($io, $this->findRouteContaining($name, $routes), [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'show_controllers' => $input->getOption('show-controllers'), + 'output' => $io, + ]); + + return 0; + } + + if (!$route && $matchingRoutes) { $default = 1 === \count($matchingRoutes) ? $matchingRoutes[0] : null; $name = $io->choice('Select one of the matching routes', $matchingRoutes, $default); $route = $routes->get($name); @@ -146,4 +160,16 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti $suggestions->suggestValues($helper->getFormats()); } } + + private function findRouteContaining(string $name, RouteCollection $routes): RouteCollection + { + $foundRoutes = new RouteCollection(); + foreach ($routes as $routeName => $route) { + if (false !== stripos($routeName, $name)) { + $foundRoutes->add($routeName, $route); + } + } + + return $foundRoutes; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php index d61b3c1345adb..da78d510a6b4c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php @@ -33,7 +33,7 @@ #[AsCommand(name: 'router:match', description: 'Help debug routes by simulating a path info match')] class RouterMatchCommand extends Command { - private $router; + private RouterInterface $router; private iterable $expressionLanguageProviders; /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php index 255de463ef6df..823c0f10d8d1a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php @@ -28,8 +28,8 @@ #[AsCommand(name: 'secrets:decrypt-to-local', description: 'Decrypt all secrets and stores them in the local vault')] final class SecretsDecryptToLocalCommand extends Command { - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php index 439e060a2d5bb..aa5d25fc8dc2d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php @@ -27,8 +27,8 @@ #[AsCommand(name: 'secrets:encrypt-from-local', description: 'Encrypt all local secrets to the vault')] final class SecretsEncryptFromLocalCommand extends Command { - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php index eeeaa2a391db6..40816665781bf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php @@ -30,8 +30,8 @@ #[AsCommand(name: 'secrets:generate-keys', description: 'Generate new encryption keys')] final class SecretsGenerateKeysCommand extends Command { - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php index 1ce6bbcb7dc89..a9d31e2217967 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php @@ -31,8 +31,8 @@ #[AsCommand(name: 'secrets:list', description: 'List all secrets')] final class SecretsListCommand extends Command { - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { @@ -70,7 +70,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $secrets = $this->vault->list($reveal); - $localSecrets = null !== $this->localVault ? $this->localVault->list($reveal) : null; + $localSecrets = $this->localVault?->list($reveal); $rows = []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php index 5e68295e715e4..dfbfcf2267469 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php @@ -32,8 +32,8 @@ #[AsCommand(name: 'secrets:remove', description: 'Remove a secret from the vault')] final class SecretsRemoveCommand extends Command { - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php index 21113b759b23f..1a0a500d89872 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php @@ -33,8 +33,8 @@ #[AsCommand(name: 'secrets:set', description: 'Set a secret in the vault')] final class SecretsSetCommand extends Command { - private $vault; - private $localVault; + private AbstractVault $vault; + private ?AbstractVault $localVault; public function __construct(AbstractVault $vault, AbstractVault $localVault = null) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index 68735f922dd40..c2cfae702863b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -50,9 +50,9 @@ class TranslationDebugCommand extends Command public const MESSAGE_UNUSED = 1; public const MESSAGE_EQUALS_FALLBACK = 2; - private $translator; - private $reader; - private $extractor; + private TranslatorInterface $translator; + private TranslationReaderInterface $reader; + private ExtractorInterface $extractor; private ?string $defaultTransPath; private ?string $defaultViewsPath; private array $transPaths; @@ -153,7 +153,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($this->defaultViewsPath) { $codePaths[] = $this->defaultViewsPath; } - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { // such a bundle does not exist, so treat the argument as path $path = $input->getArgument('bundle'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index e3128ef3c5c1c..ec399b0204c75 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -50,9 +50,9 @@ class TranslationUpdateCommand extends Command 'xlf20' => ['xlf', '2.0'], ]; - private $writer; - private $reader; - private $extractor; + private TranslationWriterInterface $writer; + private TranslationReaderInterface $reader; + private ExtractorInterface $extractor; private string $defaultLocale; private ?string $defaultTransPath; private ?string $defaultViewsPath; @@ -186,7 +186,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $codePaths[] = $this->defaultViewsPath; } $currentName = $foundBundle->getName(); - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { // such a bundle does not exist, so treat the argument as path $path = $input->getArgument('bundle'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 1e942604c995e..8927eb1e07b1d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -29,7 +29,7 @@ */ class Application extends BaseApplication { - private $kernel; + private KernelInterface $kernel; private bool $commandsRegistered = false; private array $registrationErrors = []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php index c6545f70abaa2..3c0281343d3c3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php @@ -299,7 +299,7 @@ public static function getClassDescription(string $class, string &$resolvedClass return trim(preg_replace('#\s*\n\s*\*\s*#', ' ', $docComment)); } - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { } return ''; @@ -326,7 +326,6 @@ private function getContainerEnvVars(ContainerBuilder $container): array $getDefaultParameter = $getDefaultParameter->bindTo($bag, \get_class($bag)); $getEnvReflection = new \ReflectionMethod($container, 'getEnv'); - $getEnvReflection->setAccessible(true); $envs = []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 2706881a9aec4..ae5188b53aae5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -37,7 +37,7 @@ */ class TextDescriptor extends Descriptor { - private $fileLinkFormatter; + private ?FileLinkFormatter $fileLinkFormatter; public function __construct(FileLinkFormatter $fileLinkFormatter = null) { @@ -563,7 +563,7 @@ private function formatControllerLink(mixed $controller, string $anchorText, cal } else { $r = new \ReflectionFunction($controller); } - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { if (\is_array($controller)) { $controller = implode('::', $controller); } @@ -582,7 +582,7 @@ private function formatControllerLink(mixed $controller, string $anchorText, cal try { $r = new \ReflectionMethod($container->findDefinition($id)->getClass(), $method); - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { return $anchorText; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index 9e0836101b38e..3be4b0f517411 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -42,6 +42,7 @@ use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Contracts\Service\Attribute\Required; use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; @@ -60,6 +61,7 @@ abstract class AbstractController implements ServiceSubscriberInterface /** * @required */ + #[Required] public function setContainer(ContainerInterface $container): ?ContainerInterface { $previous = $this->container; diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php index ea1517b9eeff5..992fc802231fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php @@ -27,7 +27,7 @@ */ class RedirectController { - private $router; + private ?UrlGeneratorInterface $router; private ?int $httpPort; private ?int $httpsPort; diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php index 2283dbc91fccf..4aacf24fc05a7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php @@ -23,7 +23,7 @@ */ class TemplateController { - private $twig; + private ?Environment $twig; public function __construct(Environment $twig = null) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php index ee2bbb6521b17..898f961d0a1f3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php @@ -13,6 +13,7 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Translation\TranslatorBagInterface; /** * @author Christian Flothmann @@ -27,7 +28,7 @@ public function process(ContainerBuilder $container) $translatorClass = $container->getParameterBag()->resolveValue($container->findDefinition('translator')->getClass()); - if (!is_subclass_of($translatorClass, 'Symfony\Component\Translation\TranslatorBagInterface')) { + if (!is_subclass_of($translatorClass, TranslatorBagInterface::class)) { $container->removeDefinition('translator.data_collector'); $container->removeDefinition('data_collector.translation'); } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index a556599e76d0c..558111e6a5d43 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -27,6 +27,7 @@ class UnusedTagsPass implements CompilerPassInterface 'auto_alias', 'cache.pool', 'cache.pool.clearer', + 'cache.taggable', 'chatter.transport_factory', 'config_cache.resource_checker', 'console.command', @@ -70,6 +71,7 @@ class UnusedTagsPass implements CompilerPassInterface 'property_info.list_extractor', 'property_info.type_extractor', 'proxy', + 'routing.condition_service', 'routing.expression_language_function', 'routing.expression_language_provider', 'routing.loader', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ffb9beb9a0774..3d4408153b969 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -25,6 +25,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\Form\Form; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\Lock\Lock; @@ -35,6 +36,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; +use Symfony\Component\Semaphore\Semaphore; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\Translator; use Symfony\Component\Uid\Factory\UuidFactory; @@ -81,7 +83,11 @@ public function getConfigTreeBuilder(): TreeBuilder ->info("Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. Note: When using the HttpCache, you need to call the method in your front controller instead") ->defaultTrue() ->end() - ->scalarNode('ide')->defaultNull()->end() + ->scalarNode('trust_x_sendfile_type_header') + ->info('Set true to enable support for xsendfile in binary file responses.') + ->defaultFalse() + ->end() + ->scalarNode('ide')->defaultValue('%env(default::SYMFONY_IDE)%')->end() ->booleanNode('test')->end() ->scalarNode('default_locale')->defaultValue('en')->end() ->booleanNode('set_locale_from_accept_language') @@ -153,6 +159,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addExceptionsSection($rootNode); $this->addWebLinkSection($rootNode, $enableIfStandalone); $this->addLockSection($rootNode, $enableIfStandalone); + $this->addSemaphoreSection($rootNode, $enableIfStandalone); $this->addMessengerSection($rootNode, $enableIfStandalone); $this->addRobotsIndexSection($rootNode); $this->addHttpClientSection($rootNode, $enableIfStandalone); @@ -161,6 +168,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addNotifierSection($rootNode, $enableIfStandalone); $this->addRateLimiterSection($rootNode, $enableIfStandalone); $this->addUidSection($rootNode, $enableIfStandalone); + $this->addHtmlSanitizerSection($rootNode, $enableIfStandalone); return $treeBuilder; } @@ -1278,6 +1286,61 @@ private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableI ; } + private function addSemaphoreSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + { + $rootNode + ->children() + ->arrayNode('semaphore') + ->info('Semaphore configuration') + ->{$enableIfStandalone('symfony/semaphore', Semaphore::class)}() + ->beforeNormalization() + ->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; }) + ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { return \is_array($v) && !isset($v['enabled']); }) + ->then(function ($v) { return $v + ['enabled' => true]; }) + ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { return \is_array($v) && !isset($v['resources']) && !isset($v['resource']); }) + ->then(function ($v) { + $e = $v['enabled']; + unset($v['enabled']); + + return ['enabled' => $e, 'resources' => $v]; + }) + ->end() + ->addDefaultsIfNotSet() + ->fixXmlConfig('resource') + ->children() + ->arrayNode('resources') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->requiresAtLeastOneElement() + ->beforeNormalization() + ->ifString()->then(function ($v) { return ['default' => $v]; }) + ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { return \is_array($v) && array_is_list($v); }) + ->then(function ($v) { + $resources = []; + foreach ($v as $resource) { + $resources[] = \is_array($resource) && isset($resource['name']) + ? [$resource['name'] => $resource['value']] + : ['default' => $resource] + ; + } + + return array_merge_recursive([], ...$resources); + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ; + } + private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode @@ -1426,6 +1489,7 @@ function ($a) { ->booleanNode('reset_on_message') ->defaultTrue() ->info('Reset container services after each message.') + ->setDeprecated('symfony/framework-bundle', '6.1', 'Option "%node%" at "%path%" is deprecated. It does nothing and will be removed in version 7.0.') ->validate() ->ifTrue(static fn ($v) => true !== $v) ->thenInvalid('The "framework.messenger.reset_on_message" configuration option can be set to "true" only. To prevent services resetting after each message you can set the "--no-reset" option in "messenger:consume" command.') @@ -2044,4 +2108,147 @@ private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIf ->end() ; } + + private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + { + $rootNode + ->children() + ->arrayNode('html_sanitizer') + ->info('HtmlSanitizer configuration') + ->{$enableIfStandalone('symfony/html-sanitizer', HtmlSanitizerInterface::class)}() + ->fixXmlConfig('sanitizer') + ->children() + ->scalarNode('default') + ->defaultNull() + ->info('Default sanitizer to use when injecting without named binding.') + ->end() + ->arrayNode('sanitizers') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->fixXmlConfig('allow_element') + ->fixXmlConfig('block_element') + ->fixXmlConfig('drop_element') + ->fixXmlConfig('allow_attribute') + ->fixXmlConfig('drop_attribute') + ->fixXmlConfig('force_attribute') + ->fixXmlConfig('allowed_link_scheme') + ->fixXmlConfig('allowed_link_host') + ->fixXmlConfig('allowed_media_scheme') + ->fixXmlConfig('allowed_media_host') + ->fixXmlConfig('with_attribute_sanitizer') + ->fixXmlConfig('without_attribute_sanitizer') + ->children() + ->booleanNode('allow_safe_elements') + ->info('Allows "safe" elements and attributes.') + ->defaultFalse() + ->end() + ->booleanNode('allow_all_static_elements') + ->info('Allows all static elements and attributes from the W3C Sanitizer API standard.') + ->defaultFalse() + ->end() + ->arrayNode('allow_elements') + ->info('Configures the elements that the sanitizer should retain from the input. The element name is the key, the value is either a list of allowed attributes for this element or "*" to allow the default set of attributes (https://wicg.github.io/sanitizer-api/#default-configuration).') + ->example(['i' => '*', 'a' => ['title'], 'span' => 'class']) + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->variablePrototype() + ->beforeNormalization() + ->ifArray()->then(fn ($n) => $n['attribute'] ?? $n) + ->end() + ->validate() + ->ifTrue(fn ($n): bool => !\is_string($n) && !\is_array($n)) + ->thenInvalid('The value must be either a string or an array of strings.') + ->end() + ->end() + ->end() + ->arrayNode('block_elements') + ->info('Configures elements as blocked. Blocked elements are elements the sanitizer should remove from the input, but retain their children.') + ->beforeNormalization() + ->ifString() + ->then(fn (string $n): array => (array) $n) + ->end() + ->scalarPrototype()->end() + ->end() + ->arrayNode('drop_elements') + ->info('Configures elements as dropped. Dropped elements are elements the sanitizer should remove from the input, including their children.') + ->beforeNormalization() + ->ifString() + ->then(fn (string $n): array => (array) $n) + ->end() + ->scalarPrototype()->end() + ->end() + ->arrayNode('allow_attributes') + ->info('Configures attributes as allowed. Allowed attributes are attributes the sanitizer should retain from the input.') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->variablePrototype() + ->beforeNormalization() + ->ifArray()->then(fn ($n) => $n['element'] ?? $n) + ->end() + ->end() + ->end() + ->arrayNode('drop_attributes') + ->info('Configures attributes as dropped. Dropped attributes are attributes the sanitizer should remove from the input.') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->variablePrototype() + ->beforeNormalization() + ->ifArray()->then(fn ($n) => $n['element'] ?? $n) + ->end() + ->end() + ->end() + ->arrayNode('force_attributes') + ->info('Forcefully set the values of certain attributes on certain elements.') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->scalarPrototype()->end() + ->end() + ->end() + ->booleanNode('force_https_urls') + ->info('Transforms URLs using the HTTP scheme to use the HTTPS scheme instead.') + ->defaultFalse() + ->end() + ->arrayNode('allowed_link_schemes') + ->info('Allows only a given list of schemes to be used in links href attributes.') + ->scalarPrototype()->end() + ->end() + ->arrayNode('allowed_link_hosts') + ->info('Allows only a given list of hosts to be used in links href attributes.') + ->scalarPrototype()->end() + ->end() + ->booleanNode('allow_relative_links') + ->info('Allows relative URLs to be used in links href attributes.') + ->defaultFalse() + ->end() + ->arrayNode('allowed_media_schemes') + ->info('Allows only a given list of schemes to be used in media source attributes (img, audio, video, ...).') + ->scalarPrototype()->end() + ->end() + ->arrayNode('allowed_media_hosts') + ->info('Allows only a given list of hosts to be used in media source attributes (img, audio, video, ...).') + ->scalarPrototype()->end() + ->end() + ->booleanNode('allow_relative_medias') + ->info('Allows relative URLs to be used in media source attributes (img, audio, video, ...).') + ->defaultFalse() + ->end() + ->arrayNode('with_attribute_sanitizers') + ->info('Registers custom attribute sanitizers.') + ->scalarPrototype()->end() + ->end() + ->arrayNode('without_attribute_sanitizers') + ->info('Unregisters custom attribute sanitizers.') + ->scalarPrototype()->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index eb5aa9dba0368..af64c9bc807db 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; +use Composer\InstalledVersions; use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\Reader; use Http\Client\HttpClient; @@ -26,6 +27,7 @@ use Symfony\Bridge\Twig\Extension\CsrfExtension; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader; +use Symfony\Bundle\FrameworkBundle\Routing\Attribute\AsRoutingConditionService; use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface; use Symfony\Bundle\FullStack; use Symfony\Bundle\MercureBundle\MercureBundle; @@ -71,6 +73,9 @@ use Symfony\Component\Form\FormTypeExtensionInterface; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\FormTypeInterface; +use Symfony\Component\HtmlSanitizer\HtmlSanitizer; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; use Symfony\Component\HttpClient\RetryableHttpClient; @@ -79,6 +84,8 @@ use Symfony\Component\HttpKernel\Attribute\AsController; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\UidValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; use Symfony\Component\HttpKernel\DependencyInjection\Extension; @@ -116,17 +123,20 @@ use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; +use Symfony\Component\Notifier\Bridge\Engagespot\EngagespotTransportFactory; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; use Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory; use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; +use Symfony\Component\Notifier\Bridge\FortySixElks\FortySixElksTransportFactory; use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; use Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory as MailjetNotifierTransportFactory; @@ -138,8 +148,10 @@ use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; +use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; +use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory as SendinblueNotifierTransportFactory; use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; @@ -159,6 +171,7 @@ use Symfony\Component\Notifier\Recipient\Recipient; use Symfony\Component\Notifier\Transport\TransportFactoryInterface as NotifierTransportFactoryInterface; use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; @@ -176,8 +189,15 @@ 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; +use Symfony\Component\Semaphore\SemaphoreFactory; +use Symfony\Component\Semaphore\Store\StoreFactory as SemaphoreStoreFactory; use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; +use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; @@ -188,6 +208,7 @@ 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\LocaleSwitcher; use Symfony\Component\Translation\PseudoLocalizationTranslator; use Symfony\Component\Translation\Translator; use Symfony\Component\Uid\Factory\UuidFactory; @@ -225,6 +246,7 @@ class FrameworkExtension extends Extension 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; @@ -237,7 +259,16 @@ public function load(array $configs, ContainerBuilder $container) { $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config')); + if (class_exists(InstalledVersions::class) && InstalledVersions::isInstalled('symfony/symfony') && 'symfony/symfony' !== (InstalledVersions::getRootPackage()['name'] ?? '')) { + trigger_deprecation('symfony/symfony', '6.1', 'Requiring the "symfony/symfony" package is deprecated; replace it with standalone components instead.'); + } + $loader->load('web.php'); + + if (!class_exists(BackedEnumValueResolver::class)) { + $container->removeDefinition('argument_resolver.backed_enum_resolver'); + } + $loader->load('services.php'); $loader->load('fragment_renderer.php'); $loader->load('error_renderer.php'); @@ -308,6 +339,7 @@ public function load(array $configs, ContainerBuilder $container) } $container->setParameter('kernel.http_method_override', $config['http_method_override']); + $container->setParameter('kernel.trust_x_sendfile_type_header', $config['trust_x_sendfile_type_header']); $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); $container->setParameter('kernel.default_locale', $config['default_locale']); $container->setParameter('kernel.enabled_locales', $config['enabled_locales']); @@ -365,7 +397,7 @@ public function load(array $configs, ContainerBuilder $container) $container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']); - if ($this->isConfigEnabled($container, $config['serializer'])) { + if ($this->serializerConfigEnabled = $this->isConfigEnabled($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".'); } @@ -381,6 +413,10 @@ public function load(array $configs, ContainerBuilder $container) $this->registerLockConfiguration($config['lock'], $container, $loader); } + if ($this->isConfigEnabled($container, $config['semaphore'])) { + $this->registerSemaphoreConfiguration($config['semaphore'], $container, $loader); + } + if ($this->isConfigEnabled($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".'); @@ -403,6 +439,8 @@ public function load(array $configs, ContainerBuilder $container) } $this->registerUidConfiguration($config['uid'], $container, $loader); + } else { + $container->removeDefinition('argument_resolver.uid'); } // register cache before session so both can share the connection services @@ -493,15 +531,23 @@ public function load(array $configs, ContainerBuilder $container) $this->registerNotifierConfiguration($config['notifier'], $container, $loader); } - // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier being registered + // 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 (!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".'); + } + + $this->registerHtmlSanitizerConfiguration($config['html_sanitizer'], $container, $loader); + } + $this->addAnnotatedClassesToCompile([ '**\\Controller\\', '**\\Entity\\', // Added explicitly so that we don't rely on the class map being dumped to make it work - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController', + AbstractController::class, ]); if (ContainerBuilder::willBeAvailable('symfony/mime', MimeTypes::class, ['symfony/framework-bundle'])) { @@ -600,11 +646,16 @@ public function load(array $configs, ContainerBuilder $container) $container->registerAttributeForAutoconfiguration(AsController::class, static function (ChildDefinition $definition, AsController $attribute): void { $definition->addTag('controller.service_arguments'); }); - $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute): void { + $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(); + } $definition->addTag('messenger.message_handler', $tagAttributes); }); @@ -628,6 +679,10 @@ public function load(array $configs, ContainerBuilder $container) 'kernel.locale_aware', 'kernel.reset', ]); + + $container->registerAttributeForAutoconfiguration(AsRoutingConditionService::class, static function (ChildDefinition $definition, AsRoutingConditionService $attribute): void { + $definition->addTag('routing.condition_service', (array) $attribute); + }); } /** @@ -770,6 +825,10 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $ $loader->load('notifier_debug.php'); } + if ($this->serializerConfigEnabled) { + $loader->load('serializer_debug.php'); + } + $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); $container->setParameter('profiler_listener.only_main_requests', $config['only_main_requests']); @@ -1247,6 +1306,11 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder } $loader->load('translation.php'); + + if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleSwitcher::class, ['symfony/framework-bundle'])) { + $container->removeDefinition('translation.locale_switcher'); + } + $loader->load('translation_providers.php'); // Use the "real" translator instead of the identity default @@ -1723,7 +1787,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $serializerLoaders = []; if (isset($config['enable_annotations']) && $config['enable_annotations']) { $annotationLoader = new Definition( - 'Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', + AnnotationLoader::class, [new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE)] ); $annotationLoader->setPublic(false); @@ -1732,7 +1796,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder } $fileRecorder = function ($extension, $path) use (&$serializerLoaders) { - $definition = new Definition(\in_array($extension, ['yaml', 'yml']) ? 'Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader' : 'Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader', [$path]); + $definition = new Definition(\in_array($extension, ['yaml', 'yml']) ? YamlFileLoader::class : XmlFileLoader::class, [$path]); $definition->setPublic(false); $serializerLoaders[] = $definition; }; @@ -1805,7 +1869,7 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container, } if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'], true)) { - $definition = $container->register('property_info.php_doc_extractor', 'Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor'); + $definition = $container->register('property_info.php_doc_extractor', PhpDocExtractor::class); $definition->addTag('property_info.description_extractor', ['priority' => -1000]); $definition->addTag('property_info.type_extractor', ['priority' => -1001]); } @@ -1826,11 +1890,11 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont // Generate stores $storeDefinitions = []; - foreach ($resourceStores as $storeDsn) { - $storeDsn = $container->resolveEnvPlaceholders($storeDsn, null, $usedEnvs); + foreach ($resourceStores as $resourceStore) { + $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs); $storeDefinition = new Definition(PersistingStoreInterface::class); $storeDefinition->setFactory([StoreFactory::class, 'createStore']); - $storeDefinition->setArguments([$storeDsn]); + $storeDefinition->setArguments([$resourceStore]); $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition); @@ -1861,6 +1925,39 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont } } + private function registerSemaphoreConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + { + $loader->load('semaphore.php'); + + foreach ($config['resources'] as $resourceName => $resourceStore) { + $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs); + $storeDefinition = new Definition(SemaphoreStoreInterface::class); + $storeDefinition->setFactory([SemaphoreStoreFactory::class, 'createStore']); + $storeDefinition->setArguments([$resourceStore]); + + $container->setDefinition($storeDefinitionId = '.semaphore.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition); + + // Generate factories for each resource + $factoryDefinition = new ChildDefinition('semaphore.factory.abstract'); + $factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId)); + $container->setDefinition('semaphore.'.$resourceName.'.factory', $factoryDefinition); + + // Generate services for semaphore instances + $semaphoreDefinition = new Definition(Semaphore::class); + $semaphoreDefinition->setPublic(false); + $semaphoreDefinition->setFactory([new Reference('semaphore.'.$resourceName.'.factory'), 'createSemaphore']); + $semaphoreDefinition->setArguments([$resourceName]); + + // provide alias for default resource + if ('default' === $resourceName) { + $container->setAlias('semaphore.factory', new Alias('semaphore.'.$resourceName.'.factory', false)); + $container->setAlias(SemaphoreFactory::class, new Alias('semaphore.factory', false)); + } else { + $container->registerAliasForArgument('semaphore.'.$resourceName.'.factory', SemaphoreFactory::class, $resourceName.'.semaphore.factory'); + } + } + } + private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $validationConfig) { if (!interface_exists(MessageBusInterface::class)) { @@ -2137,9 +2234,11 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con if ($isRedisTagAware && 'cache.app' === $name) { $container->setAlias('cache.app.taggable', $name); + $definition->addTag('cache.taggable', ['pool' => $name]); } elseif ($isRedisTagAware) { $tagAwareId = $name; $container->setAlias('.'.$name.'.inner', $name); + $definition->addTag('cache.taggable', ['pool' => $name]); } elseif ($pool['tags']) { if (true !== $pool['tags'] && ($config['pools'][$pool['tags']]['tags'] ?? false)) { $pool['tags'] = '.'.$pool['tags'].'.inner'; @@ -2148,6 +2247,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con ->addArgument(new Reference('.'.$name.'.inner')) ->addArgument(true !== $pool['tags'] ? new Reference($pool['tags']) : null) ->setPublic($pool['public']) + ->addTag('cache.taggable', ['pool' => $name]) ; if (method_exists(TagAwareAdapter::class, 'setLogger')) { @@ -2164,6 +2264,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con $tagAwareId = '.'.$name.'.taggable'; $container->register($tagAwareId, TagAwareAdapter::class) ->addArgument(new Reference($name)) + ->addTag('cache.taggable', ['pool' => $name]) ; } @@ -2419,17 +2520,20 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', DiscordTransportFactory::class => 'notifier.transport_factory.discord', + EngagespotTransportFactory::class => 'notifier.transport_factory.engagespot', EsendexTransportFactory::class => 'notifier.transport_factory.esendex', ExpoTransportFactory::class => 'notifier.transport_factory.expo', FakeChatTransportFactory::class => 'notifier.transport_factory.fake-chat', FakeSmsTransportFactory::class => 'notifier.transport_factory.fake-sms', FirebaseTransportFactory::class => 'notifier.transport_factory.firebase', + FortySixElksTransportFactory::class => 'notifier.transport_factory.forty-six-elks', FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile', GatewayApiTransportFactory::class => 'notifier.transport_factory.gateway-api', GitterTransportFactory::class => 'notifier.transport_factory.gitter', GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat', InfobipTransportFactory::class => 'notifier.transport_factory.infobip', IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', + KazInfoTehTransportFactory::class => 'notifier.transport_factory.kaz-info-teh', LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', MailjetNotifierTransportFactory::class => 'notifier.transport_factory.mailjet', @@ -2441,8 +2545,10 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ MobytTransportFactory::class => 'notifier.transport_factory.mobyt', OctopushTransportFactory::class => 'notifier.transport_factory.octopush', OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', + OrangeSmsTransportFactory::class => 'notifier.transport_factory.orange-sms', OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', + SendberryTransportFactory::class => 'notifier.transport_factory.sendberry', SendinblueNotifierTransportFactory::class => 'notifier.transport_factory.sendinblue', SinchTransportFactory::class => 'notifier.transport_factory.sinch', SlackTransportFactory::class => 'notifier.transport_factory.slack', @@ -2558,6 +2664,85 @@ private function registerUidConfiguration(array $config, ContainerBuilder $conta $container->getDefinition('name_based_uuid.factory') ->setArguments([$config['name_based_uuid_namespace']]); } + + if (!class_exists(UidValueResolver::class)) { + $container->removeDefinition('argument_resolver.uid'); + } + } + + private function registerHtmlSanitizerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + { + $loader->load('html_sanitizer.php'); + + foreach ($config['sanitizers'] as $sanitizerName => $sanitizerConfig) { + $configId = 'html_sanitizer.config.'.$sanitizerName; + $def = $container->register($configId, HtmlSanitizerConfig::class); + + // Base + if ($sanitizerConfig['allow_safe_elements']) { + $def->addMethodCall('allowSafeElements', [], true); + } + + if ($sanitizerConfig['allow_all_static_elements']) { + $def->addMethodCall('allowAllStaticElements', [], true); + } + + // Configures elements + foreach ($sanitizerConfig['allow_elements'] as $element => $attributes) { + $def->addMethodCall('allowElement', [$element, $attributes], true); + } + + foreach ($sanitizerConfig['block_elements'] as $element) { + $def->addMethodCall('blockElement', [$element], true); + } + + foreach ($sanitizerConfig['drop_elements'] as $element) { + $def->addMethodCall('dropElement', [$element], true); + } + + // Configures attributes + foreach ($sanitizerConfig['allow_attributes'] as $attribute => $elements) { + $def->addMethodCall('allowAttribute', [$attribute, $elements], true); + } + + foreach ($sanitizerConfig['drop_attributes'] as $attribute => $elements) { + $def->addMethodCall('dropAttribute', [$attribute, $elements], true); + } + + // Force attributes + foreach ($sanitizerConfig['force_attributes'] as $element => $attributes) { + foreach ($attributes as $attrName => $attrValue) { + $def->addMethodCall('forceAttribute', [$element, $attrName, $attrValue], true); + } + } + + // Settings + $def->addMethodCall('forceHttpsUrls', [$sanitizerConfig['force_https_urls']], true); + $def->addMethodCall('allowLinkSchemes', [$sanitizerConfig['allowed_link_schemes']], true); + $def->addMethodCall('allowLinkHosts', [$sanitizerConfig['allowed_link_hosts']], true); + $def->addMethodCall('allowRelativeLinks', [$sanitizerConfig['allow_relative_links']], true); + $def->addMethodCall('allowMediaSchemes', [$sanitizerConfig['allowed_media_schemes']], true); + $def->addMethodCall('allowMediaHosts', [$sanitizerConfig['allowed_media_hosts']], true); + $def->addMethodCall('allowRelativeMedias', [$sanitizerConfig['allow_relative_medias']], true); + + // Custom attribute sanitizers + foreach ($sanitizerConfig['with_attribute_sanitizers'] as $serviceName) { + $def->addMethodCall('withAttributeSanitizer', [new Reference($serviceName)], true); + } + + foreach ($sanitizerConfig['without_attribute_sanitizers'] as $serviceName) { + $def->addMethodCall('withoutAttributeSanitizer', [new Reference($serviceName)], true); + } + + // Create the sanitizer and link its config + $sanitizerId = 'html_sanitizer.sanitizer.'.$sanitizerName; + $container->register($sanitizerId, HtmlSanitizer::class)->addArgument(new Reference($configId)); + + $container->registerAliasForArgument($sanitizerId, HtmlSanitizerInterface::class, $sanitizerName); + } + + $default = $config['default'] ? 'html_sanitizer.sanitizer.'.$config['default'] : 'html_sanitizer'; + $container->setAlias(HtmlSanitizerInterface::class, new Reference($default)); } private function resolveTrustedHeaders(array $headers): int diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 794686e3cdf5f..3b86ea1344fcf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -44,6 +44,7 @@ use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\Form\DependencyInjection\FormPass; use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; @@ -94,6 +95,10 @@ public function boot() if ($this->container->getParameter('kernel.http_method_override')) { Request::enableHttpMethodParameterOverride(); } + + if ($this->container->hasParameter('kernel.trust_x_sendfile_type_header') && $this->container->getParameter('kernel.trust_x_sendfile_type_header')) { + BinaryFileResponse::trustXSendfileTypeHeader(); + } } public function build(ContainerBuilder $container) diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php b/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php index 1e50bd56b6337..102892b2f426c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php +++ b/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php @@ -30,8 +30,8 @@ class HttpCache extends BaseHttpCache protected $cacheDir; protected $kernel; - private $store = null; - private $surrogate; + private ?StoreInterface $store = null; + private ?SurrogateInterface $surrogate; private array $options; /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php index 59e5d2efb6aec..dbf818f13a65a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php @@ -50,8 +50,8 @@ private function configureContainer(ContainerConfigurator $container, LoaderInte { $configDir = $this->getConfigDir(); - $container->import($configDir.'/{packages}/*.yaml'); - $container->import($configDir.'/{packages}/'.$this->environment.'/*.yaml'); + $container->import($configDir.'/{packages}/*.{php,yaml}'); + $container->import($configDir.'/{packages}/'.$this->environment.'/*.{php,yaml}'); if (is_file($configDir.'/services.yaml')) { $container->import($configDir.'/services.yaml'); @@ -74,8 +74,8 @@ private function configureRoutes(RoutingConfigurator $routes): void { $configDir = $this->getConfigDir(); - $routes->import($configDir.'/{routes}/'.$this->environment.'/*.yaml'); - $routes->import($configDir.'/{routes}/*.yaml'); + $routes->import($configDir.'/{routes}/'.$this->environment.'/*.{php,yaml}'); + $routes->import($configDir.'/{routes}/*.{php,yaml}'); if (is_file($configDir.'/routes.yaml')) { $routes->import($configDir.'/routes.yaml'); @@ -146,7 +146,7 @@ public function registerContainerConfiguration(LoaderInterface $loader) ], ]); - $kernelClass = false !== strpos(static::class, "@anonymous\0") ? parent::class : static::class; + $kernelClass = str_contains(static::class, "@anonymous\0") ? parent::class : static::class; if (!$container->hasDefinition('kernel')) { $container->register('kernel', $kernelClass) @@ -214,6 +214,8 @@ public function loadRoutes(LoaderInterface $loader): RouteCollection if (\is_array($controller) && [0, 1] === array_keys($controller) && $this === $controller[0]) { $route->setDefault('_controller', ['kernel', $controller[1]]); + } elseif ($controller instanceof \Closure && $this === ($r = new \ReflectionFunction($controller))->getClosureThis() && !str_contains($r->name, '{closure}')) { + $route->setDefault('_controller', ['kernel', $r->name]); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index c80177c5d39c0..4fb751d4bfb32 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -115,7 +115,7 @@ public function loginUser(object $user, string $firewallContext = 'main'): stati } if (!$user instanceof UserInterface) { - throw new \LogicException(sprintf('The first argument of "%s" must be instance of "%s", "%s" provided.', __METHOD__, UserInterface::class, \is_object($user) ? \get_class($user) : \gettype($user))); + throw new \LogicException(sprintf('The first argument of "%s" must be instance of "%s", "%s" provided.', __METHOD__, UserInterface::class, get_debug_type($user))); } $token = new TestBrowserToken($user->getRoles(), $user, $firewallContext); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php index d1a10e2b36c4b..679d74c80dc37 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php @@ -39,6 +39,7 @@ ->set('cache.app.taggable', TagAwareAdapter::class) ->args([service('cache.app')]) + ->tag('cache.taggable', ['pool' => 'cache.app']) ->set('cache.system') ->parent('cache.adapter.system') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php index 610a83addec42..c8ff77f1e795d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php @@ -16,6 +16,7 @@ use Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand; use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand; use Symfony\Bundle\FrameworkBundle\Command\CachePoolDeleteCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolInvalidateTagsCommand; use Symfony\Bundle\FrameworkBundle\Command\CachePoolListCommand; use Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand; use Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand; @@ -93,6 +94,12 @@ ]) ->tag('console.command') + ->set('console.command.cache_pool_invalidate_tags', CachePoolInvalidateTagsCommand::class) + ->args([ + tagged_locator('cache.taggable', 'pool'), + ]) + ->tag('console.command') + ->set('console.command.cache_pool_delete', CachePoolDeleteCommand::class) ->args([ service('cache.global_clearer'), diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/html_sanitizer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/html_sanitizer.php new file mode 100644 index 0000000000000..558188d18915f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/html_sanitizer.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HtmlSanitizer\HtmlSanitizer; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('html_sanitizer.config', HtmlSanitizerConfig::class) + ->call('allowSafeElements') + + ->set('html_sanitizer', HtmlSanitizer::class) + ->args([service('html_sanitizer.config')]) + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php index 73beb2c346698..fde0533140809 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php @@ -22,6 +22,7 @@ use Symfony\Component\Notifier\ChatterInterface; use Symfony\Component\Notifier\EventListener\NotificationLoggerListener; use Symfony\Component\Notifier\EventListener\SendFailedMessageToNotifierListener; +use Symfony\Component\Notifier\FlashMessage\DefaultFlashMessageImportanceMapper; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\PushMessage; use Symfony\Component\Notifier\Message\SmsMessage; @@ -43,8 +44,11 @@ ->set('notifier.channel_policy', ChannelPolicy::class) ->args([[]]) + ->set('notifier.flash_message_importance_mapper', DefaultFlashMessageImportanceMapper::class) + ->args([[]]) + ->set('notifier.channel.browser', BrowserChannel::class) - ->args([service('request_stack')]) + ->args([service('request_stack'), service('notifier.flash_message_importance_mapper')]) ->tag('notifier.channel', ['channel' => 'browser']) ->set('notifier.channel.chat', ChatChannel::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 1af10c4fbf6f4..819177c337787 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -15,17 +15,20 @@ use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; +use Symfony\Component\Notifier\Bridge\Engagespot\EngagespotTransportFactory; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; use Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory; use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; +use Symfony\Component\Notifier\Bridge\FortySixElks\FortySixElksTransportFactory; use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; use Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; @@ -37,8 +40,10 @@ use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; +use Symfony\Component\Notifier\Bridge\OrangeSms\OrangeSmsTransportFactory; use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; +use Symfony\Component\Notifier\Bridge\Sendberry\SendberryTransportFactory; use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory; use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; @@ -104,6 +109,10 @@ ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') + ->set('notifier.transport_factory.forty-six-elks', FortySixElksTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->set('notifier.transport_factory.free-mobile', FreeMobileTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') @@ -148,6 +157,10 @@ ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.sendberry', SendberryTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->set('notifier.transport_factory.sendinblue', SendinblueTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') @@ -238,8 +251,20 @@ ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.orange-sms', OrangeSmsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->set('notifier.transport_factory.expo', ExpoTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.kaz-info-teh', KazInfoTehTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.engagespot', EngagespotTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index d48e8ca1520f7..3ffa5b571503b 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 @@ -31,6 +31,7 @@ + @@ -38,10 +39,12 @@ + + @@ -481,6 +484,21 @@ + + + + + + + + + + + + + + + @@ -802,4 +820,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/semaphore.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/semaphore.php new file mode 100644 index 0000000000000..ce35c25089548 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/semaphore.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Semaphore\SemaphoreFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('semaphore.factory.abstract', SemaphoreFactory::class)->abstract() + ->args([abstract_arg('Store')]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'semaphore']) + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer_debug.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer_debug.php new file mode 100644 index 0000000000000..28fc4ea48c514 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer_debug.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; +use Symfony\Component\Serializer\Debug\TraceableSerializer; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('debug.serializer', TraceableSerializer::class) + ->decorate('serializer', null, 255) + ->args([ + service('debug.serializer.inner'), + service('serializer.data_collector'), + ]) + + ->set('serializer.data_collector', SerializerDataCollector::class) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/serializer.html.twig', + 'id' => 'serializer', + ]) + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php index 2a6e18e835e42..f662343e331b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php @@ -200,6 +200,14 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] ]) ->tag('routing.expression_language_function', ['function' => 'env']) + ->set('container.get_routing_condition_service', \Closure::class) + ->public() + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [tagged_locator('routing.condition_service', 'alias'), 'get'], + ]) + ->tag('routing.expression_language_function', ['function' => 'service']) + // inherit from this service to lazily access env vars ->set('container.env', LazyString::class) ->abstract() diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php index 185e85838271c..30a622d02c8fb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php @@ -77,7 +77,7 @@ ->set('session.abstract_handler', AbstractSessionHandler::class) ->factory([SessionHandlerFactory::class, 'createHandler']) - ->args([abstract_arg('A string or a connection object')]) + ->args([abstract_arg('A string or a connection object'), []]) ->set('session_listener', SessionListener::class) ->args([ diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php index 706e4928ee2e0..fadaff6d2b596 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php @@ -39,11 +39,13 @@ use Symfony\Component\Translation\Loader\QtFileLoader; use Symfony\Component\Translation\Loader\XliffFileLoader; use Symfony\Component\Translation\Loader\YamlFileLoader; +use Symfony\Component\Translation\LocaleSwitcher; use Symfony\Component\Translation\LoggingTranslator; use Symfony\Component\Translation\Reader\TranslationReader; use Symfony\Component\Translation\Reader\TranslationReaderInterface; use Symfony\Component\Translation\Writer\TranslationWriter; use Symfony\Component\Translation\Writer\TranslationWriterInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; return static function (ContainerConfigurator $container) { @@ -114,6 +116,10 @@ ->set('translation.dumper.xliff', XliffFileDumper::class) ->tag('translation.dumper', ['alias' => 'xlf']) + ->set('translation.dumper.xliff.xliff', XliffFileDumper::class) + ->args(['xliff']) + ->tag('translation.dumper', ['alias' => 'xliff']) + ->set('translation.dumper.po', PoFileDumper::class) ->tag('translation.dumper', ['alias' => 'po']) @@ -158,5 +164,15 @@ ->args([service(ContainerInterface::class)]) ->tag('container.service_subscriber', ['id' => 'translator']) ->tag('kernel.cache_warmer') + + ->set('translation.locale_switcher', LocaleSwitcher::class) + ->args([ + param('kernel.default_locale'), + tagged_iterator('kernel.locale_aware'), + service('router.request_context')->ignoreOnInvalid(), + ]) + ->tag('kernel.reset', ['method' => 'reset']) + ->alias(LocaleAwareInterface::class, 'translation.locale_switcher') + ->alias(LocaleSwitcher::class, 'translation.locale_switcher') ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation_providers.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation_providers.php index cd140f077c172..ccd7a69f5f9a9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation_providers.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation_providers.php @@ -51,6 +51,7 @@ service('logger'), param('kernel.default_locale'), service('translation.loader.xliff'), + service('translator'), ]) ->tag('translation.provider_factory') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php index 53613d3b5020c..d28e88995f84c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php @@ -13,11 +13,14 @@ use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\UidValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; use Symfony\Component\HttpKernel\Controller\ErrorController; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; @@ -25,7 +28,6 @@ use Symfony\Component\HttpKernel\EventListener\ErrorListener; use Symfony\Component\HttpKernel\EventListener\LocaleListener; use Symfony\Component\HttpKernel\EventListener\ResponseListener; -use Symfony\Component\HttpKernel\EventListener\StreamedResponseListener; use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; return static function (ContainerConfigurator $container) { @@ -45,6 +47,15 @@ abstract_arg('argument value resolvers'), ]) + ->set('argument_resolver.backed_enum_resolver', BackedEnumValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 100]) + + ->set('argument_resolver.uid', UidValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 100]) + + ->set('argument_resolver.datetime', DateTimeValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 100]) + ->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class) ->tag('controller.argument_value_resolver', ['priority' => 100]) @@ -73,9 +84,6 @@ ]) ->tag('kernel.event_subscriber') - ->set('streamed_response_listener', StreamedResponseListener::class) - ->tag('kernel.event_subscriber') - ->set('locale_listener', LocaleListener::class) ->args([ service('request_stack'), diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Attribute/AsRoutingConditionService.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Attribute/AsRoutingConditionService.php new file mode 100644 index 0000000000000..7467913e21c05 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Attribute/AsRoutingConditionService.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\Bundle\FrameworkBundle\Routing\Attribute; + +/** + * Service tag to autoconfigure routing condition services. + * + * You can tag a service: + * + * #[AsRoutingConditionService('foo')] + * class SomeFooService + * { + * public function bar(): bool + * { + * // ... + * } + * } + * + * Then you can use the tagged service in the routing condition: + * + * class PageController + * { + * #[Route('/page', condition: "service('foo').bar()")] + * public function page(): Response + * { + * // ... + * } + * } + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsRoutingConditionService +{ + public function __construct( + public ?string $alias = null, + public int $priority = 0, + ) { + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index cb442ca7ccdaf..8d3c33d64c3ee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -33,7 +33,7 @@ */ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberInterface { - private $container; + private ContainerInterface $container; private array $collectedParameters = []; private \Closure $paramFetcher; @@ -49,9 +49,9 @@ public function __construct(ContainerInterface $container, mixed $resource, arra $this->setOptions($options); if ($parameters) { - $this->paramFetcher = \Closure::fromCallable([$parameters, 'get']); + $this->paramFetcher = $parameters->get(...); } elseif ($container instanceof SymfonyContainerInterface) { - $this->paramFetcher = \Closure::fromCallable([$container, 'getParameter']); + $this->paramFetcher = $container->getParameter(...); } else { throw new \LogicException(sprintf('You should either pass a "%s" instance or provide the $parameters argument of the "%s" method.', SymfonyContainerInterface::class, __METHOD__)); } @@ -76,7 +76,7 @@ public function getRouteCollection(): RouteCollection } else { $this->collection->addResource(new FileExistenceResource($containerFile)); } - } catch (ParameterNotFoundException $exception) { + } catch (ParameterNotFoundException) { } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php index 1f8cbff6693f2..ff1cd7fb87908 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php +++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php @@ -51,7 +51,7 @@ public function generateKeys(bool $override = false): bool try { $this->loadKeys(); - } catch (\RuntimeException $e) { + } catch (\RuntimeException) { // ignore failures to load keys } diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php index 822b35ea75d57..7e9b90ef33d30 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -82,6 +82,8 @@ protected static function bootKernel(array $options = []): KernelInterface * used by other services. * * Using this method is the best way to get a container from your test code. + * + * @return TestContainer */ protected static function getContainer(): ContainerInterface { diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php index 4b5ede1eaf6d1..321ea373d85f1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php @@ -28,7 +28,7 @@ */ class TestContainer extends Container { - private $kernel; + private KernelInterface $kernel; private string $privateServicesLocatorId; public function __construct(KernelInterface $kernel, string $privateServicesLocatorId) diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php index 1f7f0802d3ce1..de31d4ba92c94 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php @@ -45,7 +45,7 @@ protected static function createClient(array $options = [], array $server = []): try { $client = $kernel->getContainer()->get('test.client'); - } catch (ServiceNotFoundException $e) { + } catch (ServiceNotFoundException) { if (class_exists(KernelBrowser::class)) { throw new \LogicException('You cannot create the client used in functional tests if the "framework.test" config is not set to true.'); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/AnnotationsCacheWarmerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/AnnotationsCacheWarmerTest.php index 8253c525df8f6..75e6ce8f7271b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/AnnotationsCacheWarmerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/AnnotationsCacheWarmerTest.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\Bundle\FrameworkBundle\Tests\CacheWarmer; use Doctrine\Common\Annotations\AnnotationReader; @@ -151,10 +160,7 @@ public function testWarmupRemoveCacheMisses() $this->assertTrue(isset($data[0]['bar_hit'])); } - /** - * @return MockObject&Reader - */ - private function getReadOnlyReader(): Reader + private function getReadOnlyReader(): MockObject&Reader { $readerMock = $this->createMock(Reader::class); $readerMock->expects($this->exactly(0))->method('getClassAnnotations'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolClearCommandTest.php index 169fcd8c2d75d..722199f227296 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolClearCommandTest.php @@ -52,10 +52,7 @@ public function provideCompletionSuggestions() ]; } - /** - * @return MockObject&KernelInterface - */ - private function getKernel(): KernelInterface + private function getKernel(): MockObject&KernelInterface { $container = $this->createMock(ContainerInterface::class); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolDeleteCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolDeleteCommandTest.php index f643bc1259901..24371b9f532dd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolDeleteCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolDeleteCommandTest.php @@ -106,10 +106,7 @@ public function provideCompletionSuggestions() ]; } - /** - * @return MockObject&KernelInterface - */ - private function getKernel(): KernelInterface + private function getKernel(): MockObject&KernelInterface { $container = $this->createMock(ContainerInterface::class); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolInvalidateTagsCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolInvalidateTagsCommandTest.php new file mode 100644 index 0000000000000..d0286b7e16faa --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePoolInvalidateTagsCommandTest.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Command; + +use Symfony\Bundle\FrameworkBundle\Command\CachePoolInvalidateTagsCommand; +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Tester\CommandCompletionTester; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +class CachePoolInvalidateTagsCommandTest extends TestCase +{ + public function testComplete() + { + $tester = new CommandCompletionTester($this->createCommand(['foo' => null, 'bar' => null])); + + $suggestions = $tester->complete(['--pool=']); + + $this->assertSame(['foo', 'bar'], $suggestions); + } + + public function testInvalidatesTagsForAllPoolsByDefault() + { + $tagsToInvalidate = ['tag1', 'tag2']; + + $foo = $this->createMock(TagAwareCacheInterface::class); + $foo->expects($this->once())->method('invalidateTags')->with($tagsToInvalidate)->willReturn(true); + + $bar = $this->createMock(TagAwareCacheInterface::class); + $bar->expects($this->once())->method('invalidateTags')->with($tagsToInvalidate)->willReturn(true); + + $tester = new CommandTester($this->createCommand([ + 'foo' => $foo, + 'bar' => $bar, + ])); + + $ret = $tester->execute(['tags' => $tagsToInvalidate]); + + $this->assertSame(Command::SUCCESS, $ret); + } + + public function testCanInvalidateSpecificPools() + { + $tagsToInvalidate = ['tag1', 'tag2']; + + $foo = $this->createMock(TagAwareCacheInterface::class); + $foo->expects($this->once())->method('invalidateTags')->with($tagsToInvalidate)->willReturn(true); + + $bar = $this->createMock(TagAwareCacheInterface::class); + $bar->expects($this->never())->method('invalidateTags'); + + $tester = new CommandTester($this->createCommand([ + 'foo' => $foo, + 'bar' => $bar, + ])); + + $ret = $tester->execute(['tags' => $tagsToInvalidate, '--pool' => ['foo']]); + + $this->assertSame(Command::SUCCESS, $ret); + } + + public function testCommandFailsIfPoolNotFound() + { + $tagsToInvalidate = ['tag1', 'tag2']; + + $foo = $this->createMock(TagAwareCacheInterface::class); + $foo->expects($this->once())->method('invalidateTags')->with($tagsToInvalidate)->willReturn(true); + + $bar = $this->createMock(TagAwareCacheInterface::class); + $bar->expects($this->never())->method('invalidateTags'); + + $tester = new CommandTester($this->createCommand([ + 'foo' => $foo, + 'bar' => $bar, + ])); + + $ret = $tester->execute(['tags' => $tagsToInvalidate, '--pool' => ['invalid', 'foo']]); + + $this->assertSame(Command::FAILURE, $ret); + } + + public function testCommandFailsIfPoolNotTaggable() + { + $tagsToInvalidate = ['tag1', 'tag2']; + + $foo = new \stdClass(); + + $bar = $this->createMock(TagAwareCacheInterface::class); + $bar->expects($this->once())->method('invalidateTags')->with($tagsToInvalidate)->willReturn(true); + + $tester = new CommandTester($this->createCommand([ + 'foo' => $foo, + 'bar' => $bar, + ])); + + $ret = $tester->execute(['tags' => $tagsToInvalidate]); + + $this->assertSame(Command::FAILURE, $ret); + } + + public function testCommandFailsIfInvalidatingTagsFails() + { + $tagsToInvalidate = ['tag1', 'tag2']; + + $foo = $this->createMock(TagAwareCacheInterface::class); + $foo->expects($this->once())->method('invalidateTags')->with($tagsToInvalidate)->willReturn(false); + + $bar = $this->createMock(TagAwareCacheInterface::class); + $bar->expects($this->once())->method('invalidateTags')->with($tagsToInvalidate)->willReturn(true); + + $tester = new CommandTester($this->createCommand([ + 'foo' => $foo, + 'bar' => $bar, + ])); + + $ret = $tester->execute(['tags' => $tagsToInvalidate]); + + $this->assertSame(Command::FAILURE, $ret); + } + + private function createCommand(array $services): CachePoolInvalidateTagsCommand + { + return new CachePoolInvalidateTagsCommand( + new ServiceLocator(array_map(fn ($service) => fn () => $service, $services)) + ); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php index 32d60124ebb5a..983956fcb9b82 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CachePruneCommandTest.php @@ -50,10 +50,7 @@ private function getEmptyRewindableGenerator(): RewindableGenerator }, 0); } - /** - * @return MockObject&KernelInterface - */ - private function getKernel(): KernelInterface + private function getKernel(): MockObject&KernelInterface { $container = $this->createMock(ContainerInterface::class); @@ -71,10 +68,7 @@ private function getKernel(): KernelInterface return $kernel; } - /** - * @return MockObject&PruneableInterface - */ - private function getPruneableInterfaceMock(): PruneableInterface + private function getPruneableInterfaceMock(): MockObject&PruneableInterface { $pruneable = $this->createMock(PruneableInterface::class); $pruneable diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRemoveCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRemoveCommandTest.php index 213e639f06698..c02bc91761084 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRemoveCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsRemoveCommandTest.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\Bundle\FrameworkBundle\Tests\Command; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsSetCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsSetCommandTest.php index 4f0d2225d148a..135907b374d26 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsSetCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsSetCommandTest.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\Bundle\FrameworkBundle\Tests\Command; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php index 3a38da0d14ed6..c4a674b669b18 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php @@ -122,9 +122,7 @@ public function getDescribeContainerDefinitionWithArgumentsShownTestData() $definitionsWithArgs[str_replace('definition_', 'definition_arguments_', $key)] = $definition; } - if (\PHP_VERSION_ID >= 80100) { - $definitionsWithArgs['definition_arguments_with_enum'] = (new Definition('definition_with_enum'))->setArgument(0, FooUnitEnum::FOO); - } + $definitionsWithArgs['definition_arguments_with_enum'] = (new Definition('definition_with_enum'))->setArgument(0, FooUnitEnum::FOO); return $this->getDescriptionTestData($definitionsWithArgs); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php index a9fc9252bf350..5987e7812a1f4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php @@ -71,10 +71,6 @@ public static function getContainerParameters() 'array' => [12, 'Hello world!', true], ]); - if (\PHP_VERSION_ID < 80100) { - return; - } - yield 'parameters_enums' => new ParameterBag([ 'unit_enum' => FooUnitEnum::BAR, 'backed_enum' => Suit::Hearts, @@ -259,7 +255,7 @@ public static function getCallables() 'callable_5' => ['Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\ExtendedCallableClass', 'parent::staticMethod'], 'callable_6' => function () { return 'Closure'; }, 'callable_7' => new CallableClass(), - 'callable_from_callable' => \Closure::fromCallable(new CallableClass()), + 'callable_from_callable' => (new CallableClass())(...), ]; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index aaae64bac01d4..07612df737190 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HtmlSanitizer\HtmlSanitizer; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\Lock\Store\SemaphoreStore; use Symfony\Component\Mailer\Mailer; @@ -265,6 +266,57 @@ public function testLockMergeConfigs() ); } + /** + * @dataProvider provideValidSemaphoreConfigurationTests + */ + public function testValidSemaphoreConfiguration($semaphoreConfig, $processedConfig) + { + $processor = new Processor(); + $configuration = new Configuration(true); + $config = $processor->processConfiguration($configuration, [ + [ + 'semaphore' => $semaphoreConfig, + ], + ]); + + $this->assertArrayHasKey('semaphore', $config); + + $this->assertEquals($processedConfig, $config['semaphore']); + } + + public function provideValidSemaphoreConfigurationTests() + { + yield [null, ['enabled' => true, 'resources' => []]]; + + yield ['redis://default', ['enabled' => true, 'resources' => ['default' => 'redis://default']]]; + yield [['foo' => 'redis://foo', 'bar' => 'redis://bar'], ['enabled' => true, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']]]; + yield [['default' => 'redis://default'], ['enabled' => true, 'resources' => ['default' => 'redis://default']]]; + + yield [['enabled' => false, 'redis://default'], ['enabled' => false, 'resources' => ['default' => 'redis://default']]]; + yield [['enabled' => false, 'foo' => 'redis://foo', 'bar' => 'redis://bar'], ['enabled' => false, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']]]; + yield [['enabled' => false, 'default' => 'redis://default'], ['enabled' => false, 'resources' => ['default' => 'redis://default']]]; + + yield [['resources' => 'redis://default'], ['enabled' => true, 'resources' => ['default' => 'redis://default']]]; + yield [['resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']], ['enabled' => true, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']]]; + yield [['resources' => ['default' => 'redis://default']], ['enabled' => true, 'resources' => ['default' => 'redis://default']]]; + + yield [['enabled' => false, 'resources' => 'redis://default'], ['enabled' => false, 'resources' => ['default' => 'redis://default']]]; + yield [['enabled' => false, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']], ['enabled' => false, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']]]; + yield [['enabled' => false, 'resources' => ['default' => 'redis://default']], ['enabled' => false, 'resources' => ['default' => 'redis://default']]]; + + // xml + + yield [['resource' => ['redis://default']], ['enabled' => true, 'resources' => ['default' => 'redis://default']]]; + yield [['resource' => ['redis://default', ['name' => 'foo', 'value' => 'redis://default']]], ['enabled' => true, 'resources' => ['default' => 'redis://default', 'foo' => 'redis://default']]]; + yield [['resource' => [['name' => 'foo', 'value' => 'redis://default']]], ['enabled' => true, 'resources' => ['foo' => 'redis://default']]]; + yield [['resource' => [['name' => 'foo', 'value' => 'redis://default'], ['name' => 'bar', 'value' => 'redis://default']]], ['enabled' => true, 'resources' => ['foo' => 'redis://default', 'bar' => 'redis://default']]]; + + yield [['enabled' => false, 'resource' => ['redis://default']], ['enabled' => false, 'resources' => ['default' => 'redis://default']]]; + yield [['enabled' => false, 'resource' => ['redis://default', ['name' => 'foo', 'value' => 'redis://default']]], ['enabled' => false, 'resources' => ['default' => 'redis://default', 'foo' => 'redis://default']]]; + yield [['enabled' => false, 'resource' => [['name' => 'foo', 'value' => 'redis://default']]], ['enabled' => false, 'resources' => ['foo' => 'redis://default']]]; + yield [['enabled' => false, 'resource' => [['name' => 'foo', 'value' => 'redis://foo'], ['name' => 'bar', 'value' => 'redis://bar']]], ['enabled' => false, 'resources' => ['foo' => 'redis://foo', 'bar' => 'redis://bar']]]; + } + public function testItShowANiceMessageIfTwoMessengerBusesAreConfiguredButNoDefaultBus() { $expectedMessage = 'You must specify the "default_bus" if you define more than one bus.'; @@ -369,7 +421,8 @@ protected static function getBundleDefaultConfig() { return [ 'http_method_override' => true, - 'ide' => null, + 'trust_x_sendfile_type_header' => false, + 'ide' => '%env(default::SYMFONY_IDE)%', 'default_locale' => 'en', 'enabled_locales' => [], 'set_locale_from_accept_language' => false, @@ -524,6 +577,11 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor ], ], ], + 'semaphore' => [ + 'enabled' => !class_exists(FullStack::class), + 'resources' => [ + ], + ], 'messenger' => [ 'enabled' => !class_exists(FullStack::class) && interface_exists(MessageBusInterface::class), 'routing' => [], @@ -582,6 +640,11 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'name_based_uuid_version' => 5, 'time_based_uuid_version' => 6, ], + 'html_sanitizer' => [ + 'enabled' => !class_exists(FullStack::class) && class_exists(HtmlSanitizer::class), + 'default' => null, + 'sanitizers' => [], + ], 'exceptions' => [], ]; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index 52903cd0b12a7..fd04b996da496 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -11,6 +11,7 @@ ], ], 'http_method_override' => false, + 'trust_x_sendfile_type_header' => true, 'esi' => [ 'enabled' => true, ], @@ -79,4 +80,7 @@ 'pdf' => 'application/pdf', ], ], + 'html_sanitizer' => [ + 'enabled' => true, + ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/html_sanitizer.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/html_sanitizer.php new file mode 100644 index 0000000000000..e7b1bd41fc360 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/html_sanitizer.php @@ -0,0 +1,47 @@ +loadFromExtension('framework', [ + 'html_sanitizer' => [ + 'default' => 'my.sanitizer', + 'sanitizers' => [ + 'my.sanitizer' => [ + 'allow_safe_elements' => true, + 'allow_all_static_elements' => true, + 'allow_elements' => [ + 'iframe' => 'src', + 'custom-tag' => ['data-attr', 'data-attr-1'], + 'custom-tag-2' => '*', + ], + 'block_elements' => ['section'], + 'drop_elements' => ['video'], + 'allow_attributes' => [ + 'src' => ['iframe'], + 'data-attr' => '*', + ], + 'drop_attributes' => [ + 'data-attr' => ['custom-tag'], + 'data-attr-1' => [], + 'data-attr-2' => '*', + ], + 'force_attributes' => [ + 'a' => ['rel' => 'noopener noreferrer'], + 'h1' => ['class' => 'bp4-heading'], + ], + 'force_https_urls' => true, + 'allowed_link_schemes' => ['http', 'https', 'mailto'], + 'allowed_link_hosts' => ['symfony.com'], + 'allow_relative_links' => true, + 'allowed_media_schemes' => ['http', 'https', 'data'], + 'allowed_media_hosts' => ['symfony.com'], + 'allow_relative_medias' => true, + 'with_attribute_sanitizers' => [ + 'App\\Sanitizer\\CustomAttributeSanitizer', + ], + 'without_attribute_sanitizers' => [ + 'App\\Sanitizer\\OtherCustomAttributeSanitizer', + ], + ], + 'all.sanitizer' => null, + ], + ], +]); 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 2e115a5aebbc0..00da2a6c0f963 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -6,7 +6,7 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + fr en diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/html_sanitizer.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/html_sanitizer.xml new file mode 100644 index 0000000000000..05cf704dd2c6c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/html_sanitizer.xml @@ -0,0 +1,63 @@ + + + + + + + + + src + + + data-attr + data-attr-1 + + + * + + section + video + + iframe + + + * + + + custom-tag + + + + * + + + noopener noreferrer + + + bp4-heading + + http + https + mailto + symfony.com + http + https + data + symfony.com + App\Sanitizer\CustomAttributeSanitizer + App\Sanitizer\OtherCustomAttributeSanitizer + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/semaphore.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/semaphore.xml new file mode 100644 index 0000000000000..0b2f6c662dcf5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/semaphore.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 6da130bbec556..67b10bb5f833e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -7,6 +7,7 @@ framework: csrf_protection: field_name: _csrf http_method_override: false + trust_x_sendfile_type_header: true esi: enabled: true ssi: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/html_sanitizer.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/html_sanitizer.yml new file mode 100644 index 0000000000000..1c4f5dfcd5a4b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/html_sanitizer.yml @@ -0,0 +1,40 @@ +framework: + html_sanitizer: + default: my.sanitizer + sanitizers: + my.sanitizer: + allow_safe_elements: true + allow_all_static_elements: true + allow_elements: + iframe: 'src' + custom-tag: ['data-attr', 'data-attr-1'] + custom-tag-2: '*' + block_elements: + - section + drop_elements: + - video + allow_attributes: + src: ['iframe'] + data-attr: '*' + drop_attributes: + data-attr: [custom-tag] + data-attr-1: [] + data-attr-2: '*' + force_attributes: + a: + rel: noopener noreferrer + h1: + class: bp4-heading + force_https_urls: true + allowed_link_schemes: ['http', 'https', 'mailto'] + allowed_link_hosts: ['symfony.com'] + allow_relative_links: true + allowed_media_schemes: ['http', 'https', 'data'] + allowed_media_hosts: ['symfony.com'] + allow_relative_medias: true + with_attribute_sanitizers: + - App\Sanitizer\CustomAttributeSanitizer + without_attribute_sanitizers: + - App\Sanitizer\OtherCustomAttributeSanitizer + + all.sanitizer: null diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/semaphore.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/semaphore.yml new file mode 100644 index 0000000000000..47b1323517b4c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/semaphore.yml @@ -0,0 +1,2 @@ +framework: + semaphore: redis://localhost diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/semaphore_named.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/semaphore_named.yml new file mode 100644 index 0000000000000..0a29e4ea825e2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/semaphore_named.yml @@ -0,0 +1,7 @@ +parameters: + env(REDIS_DSN): redis://paas.com + +framework: + semaphore: + foo: redis://paas.com + qux: "%env(REDIS_DSN)%" diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 248eeacd0102a..e99bd83ba2028 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -14,6 +14,7 @@ use Doctrine\Common\Annotations\Annotation; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerAwareInterface; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension; use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage; @@ -31,6 +32,8 @@ use Symfony\Component\Cache\DependencyInjection\CachePoolPass; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass; @@ -44,6 +47,9 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Finder\Finder; use Symfony\Component\Form\Form; +use Symfony\Component\HtmlSanitizer\HtmlSanitizer; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\RetryableHttpClient; use Symfony\Component\HttpClient\ScopingHttpClient; @@ -63,6 +69,7 @@ use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\DependencyInjection\TranslatorPass; +use Symfony\Component\Translation\LocaleSwitcher; use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\Validation; @@ -76,6 +83,8 @@ abstract class FrameworkExtensionTest extends TestCase { + use ExpectDeprecationTrait; + private static $containerCache = []; abstract protected function loadFromFile(ContainerBuilder $container, $file); @@ -172,6 +181,13 @@ public function testHttpMethodOverride() $this->assertFalse($container->getParameter('kernel.http_method_override')); } + public function testTrustXSendfileTypeHeader() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->getParameter('kernel.trust_x_sendfile_type_header')); + } + public function testEsi() { $container = $this->createContainerFromFile('full'); @@ -707,6 +723,26 @@ public function testMessengerServicesRemovedWhenDisabled() $this->assertFalse($container->hasDefinition('cache.messenger.restart_workers_signal')); } + /** + * @group legacy + */ + public function testMessengerWithExplictResetOnMessageLegacy() + { + $this->expectDeprecation('Since symfony/framework-bundle 6.1: Option "reset_on_message" at "framework.messenger" is deprecated. It does nothing and will be removed in version 7.0.'); + + $container = $this->createContainerFromFile('messenger_with_explict_reset_on_message_legacy'); + + $this->assertTrue($container->hasDefinition('console.command.messenger_consume_messages')); + $this->assertTrue($container->hasAlias('messenger.default_bus')); + $this->assertTrue($container->getAlias('messenger.default_bus')->isPublic()); + $this->assertTrue($container->hasDefinition('messenger.transport.amqp.factory')); + $this->assertTrue($container->hasDefinition('messenger.transport.redis.factory')); + $this->assertTrue($container->hasDefinition('messenger.transport_factory')); + $this->assertSame(TransportFactory::class, $container->getDefinition('messenger.transport_factory')->getClass()); + $this->assertTrue($container->hasDefinition('messenger.listener.reset_services')); + $this->assertSame('messenger.listener.reset_services', (string) $container->getDefinition('console.command.messenger_consume_messages')->getArgument(5)); + } + public function testMessenger() { $container = $this->createContainerFromFile('messenger'); @@ -967,6 +1003,17 @@ public function testMessengerInvalidTransportRouting() $this->createContainerFromFile('messenger_routing_invalid_transport'); } + /** + * @group legacy + */ + public function testMessengerWithDisabledResetOnMessage() + { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('The "framework.messenger.reset_on_message" configuration option can be set to "true" only. To prevent services resetting after each message you can set the "--no-reset" option in "messenger:consume" command.'); + + $this->createContainerFromFile('messenger_with_disabled_reset_on_message'); + } + public function testTranslator() { $container = $this->createContainerFromFile('full'); @@ -1627,6 +1674,55 @@ public function appRedisTagAwareConfigProvider(): array ]; } + public function testCacheTaggableTagAppliedToPools() + { + $container = $this->createContainerFromFile('cache'); + + $servicesToCheck = [ + 'cache.app.taggable' => 'cache.app', + 'cache.redis_tag_aware.bar' => 'cache.redis_tag_aware.bar', + '.cache.foobar.taggable' => 'cache.foobar', + ]; + + foreach ($servicesToCheck as $id => $expectedPool) { + $this->assertTrue($container->hasDefinition($id)); + + $def = $container->getDefinition($id); + + $this->assertTrue($def->hasTag('cache.taggable')); + $this->assertSame($expectedPool, $def->getTag('cache.taggable')[0]['pool'] ?? null); + } + } + + /** + * @dataProvider appRedisTagAwareConfigProvider + */ + public function testCacheTaggableTagAppliedToRedisAwareAppPool(string $configFile) + { + $container = $this->createContainerFromFile($configFile); + + $def = $container->getDefinition('cache.app'); + + $this->assertTrue($def->hasTag('cache.taggable')); + $this->assertSame('cache.app', $def->getTag('cache.taggable')[0]['pool'] ?? null); + } + + public function testCachePoolInvalidateTagsCommandRegistered() + { + $container = $this->createContainerFromFile('cache'); + $this->assertTrue($container->hasDefinition('console.command.cache_pool_invalidate_tags')); + + $locator = $container->getDefinition('console.command.cache_pool_invalidate_tags')->getArgument(0); + $this->assertInstanceOf(ServiceLocatorArgument::class, $locator); + + $iterator = $locator->getTaggedIteratorArgument(); + $this->assertInstanceOf(TaggedIteratorArgument::class, $iterator); + + $this->assertSame('cache.taggable', $iterator->getTag()); + $this->assertSame('pool', $iterator->getIndexAttribute()); + $this->assertTrue($iterator->needsIndexes()); + } + public function testRemovesResourceCheckerConfigCacheFactoryArgumentOnlyIfNoDebug() { $container = $this->createContainer(['kernel.debug' => true]); @@ -1916,6 +2012,98 @@ public function testIfNotifierTransportsAreKnownByFrameworkExtension() } } + public function testLocaleSwitcherServiceRegistered() + { + if (!class_exists(LocaleSwitcher::class)) { + $this->markTestSkipped('LocaleSwitcher not available.'); + } + + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->has('translation.locale_switcher')); + + $switcherDef = $container->getDefinition('translation.locale_switcher'); + + $this->assertSame('%kernel.default_locale%', $switcherDef->getArgument(0)); + $this->assertInstanceOf(TaggedIteratorArgument::class, $switcherDef->getArgument(1)); + $this->assertSame('kernel.locale_aware', $switcherDef->getArgument(1)->getTag()); + $this->assertEquals(new Reference('router.request_context', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $switcherDef->getArgument(2)); + } + + public function testHtmlSanitizer() + { + $container = $this->createContainerFromFile('html_sanitizer'); + + // html_sanitizer service + $this->assertTrue($container->hasDefinition('html_sanitizer'), '->registerHtmlSanitizerConfiguration() loads html_sanitizer.php'); + $this->assertSame(HtmlSanitizer::class, $container->getDefinition('html_sanitizer')->getClass()); + $this->assertCount(1, $args = $container->getDefinition('html_sanitizer')->getArguments()); + $this->assertSame('html_sanitizer.config', (string) $args[0]); + + // html_sanitizer.config service + $this->assertTrue($container->hasDefinition('html_sanitizer.config'), '->registerHtmlSanitizerConfiguration() loads html_sanitizer.php'); + $this->assertSame(HtmlSanitizerConfig::class, $container->getDefinition('html_sanitizer.config')->getClass()); + $this->assertCount(1, $calls = $container->getDefinition('html_sanitizer.config')->getMethodCalls()); + $this->assertSame(['allowSafeElements', []], $calls[0]); + + // my.sanitizer + $this->assertTrue($container->hasDefinition('html_sanitizer.sanitizer.my.sanitizer'), '->registerHtmlSanitizerConfiguration() loads custom sanitizer'); + $this->assertSame(HtmlSanitizer::class, $container->getDefinition('html_sanitizer.sanitizer.my.sanitizer')->getClass()); + $this->assertCount(1, $args = $container->getDefinition('html_sanitizer.sanitizer.my.sanitizer')->getArguments()); + $this->assertSame('html_sanitizer.config.my.sanitizer', (string) $args[0]); + + // my.sanitizer config + $this->assertTrue($container->hasDefinition('html_sanitizer.config.my.sanitizer'), '->registerHtmlSanitizerConfiguration() loads custom sanitizer'); + $this->assertSame(HtmlSanitizerConfig::class, $container->getDefinition('html_sanitizer.config.my.sanitizer')->getClass()); + $this->assertCount(23, $calls = $container->getDefinition('html_sanitizer.config.my.sanitizer')->getMethodCalls()); + $this->assertSame( + [ + ['allowSafeElements', [], true], + ['allowAllStaticElements', [], true], + ['allowElement', ['iframe', 'src'], true], + ['allowElement', ['custom-tag', ['data-attr', 'data-attr-1']], true], + ['allowElement', ['custom-tag-2', '*'], true], + ['blockElement', ['section'], true], + ['dropElement', ['video'], true], + ['allowAttribute', ['src', $this instanceof XmlFrameworkExtensionTest ? 'iframe' : ['iframe']], true], + ['allowAttribute', ['data-attr', '*'], true], + ['dropAttribute', ['data-attr', $this instanceof XmlFrameworkExtensionTest ? 'custom-tag' : ['custom-tag']], true], + ['dropAttribute', ['data-attr-1', []], true], + ['dropAttribute', ['data-attr-2', '*'], true], + ['forceAttribute', ['a', 'rel', 'noopener noreferrer'], true], + ['forceAttribute', ['h1', 'class', 'bp4-heading'], true], + ['forceHttpsUrls', [true], true], + ['allowLinkSchemes', [['http', 'https', 'mailto']], true], + ['allowLinkHosts', [['symfony.com']], true], + ['allowRelativeLinks', [true], true], + ['allowMediaSchemes', [['http', 'https', 'data']], true], + ['allowMediaHosts', [['symfony.com']], true], + ['allowRelativeMedias', [true], true], + ['withAttributeSanitizer', ['@App\\Sanitizer\\CustomAttributeSanitizer'], true], + ['withoutAttributeSanitizer', ['@App\\Sanitizer\\OtherCustomAttributeSanitizer'], true], + ], + + // Convert references to their names for easier assertion + array_map( + static function ($call) { + foreach ($call[1] as $k => $arg) { + $call[1][$k] = $arg instanceof Reference ? '@'.$arg : $arg; + } + + return $call; + }, + $calls + ) + ); + + // Named alias + $this->assertSame('html_sanitizer.sanitizer.my.sanitizer', (string) $container->getAlias(HtmlSanitizerInterface::class.' $mySanitizer'), '->registerHtmlSanitizerConfiguration() creates appropriate named alias'); + $this->assertSame('html_sanitizer.sanitizer.all.sanitizer', (string) $container->getAlias(HtmlSanitizerInterface::class.' $allSanitizer'), '->registerHtmlSanitizerConfiguration() creates appropriate named alias'); + + // Default alias + $this->assertSame('html_sanitizer.sanitizer.my.sanitizer', (string) $container->getAlias(HtmlSanitizerInterface::class), '->registerHtmlSanitizerConfiguration() creates appropriate default alias'); + } + protected function createContainer(array $data = []) { return new ContainerBuilder(new EnvPlaceholderParameterBag(array_merge([ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DefaultConfigTestBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DefaultConfigTestBundle.php index 8c7a89574729f..2a53127eaa41a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DefaultConfigTestBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DefaultConfigTestBundle.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\Bundle\FrameworkBundle\Tests\Functional\Bundle\DefaultConfigTestBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/Configuration.php index 0c0812f3f9553..ddd1589495139 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/Configuration.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\Bundle\FrameworkBundle\Tests\Functional\Bundle\DefaultConfigTestBundle\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php index d380bcaad17fa..400384f616f29 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php @@ -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\Bundle\FrameworkBundle\Tests\Functional\Bundle\DefaultConfigTestBundle\DependencyInjection; use Symfony\Component\DependencyInjection\ContainerBuilder; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php index 265e9bb138fbf..497a7a99d6d2b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php @@ -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\Bundle\FrameworkBundle\Tests\Functional\Bundle\ExtensionWithoutConfigTestBundle\DependencyInjection; use Symfony\Component\DependencyInjection\ContainerBuilder; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/ExtensionWithoutConfigTestBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/ExtensionWithoutConfigTestBundle.php index 5fe9fcdb7799f..71fae639a1db6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/ExtensionWithoutConfigTestBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/ExtensionWithoutConfigTestBundle.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\Bundle\FrameworkBundle\Tests\Functional\Bundle\ExtensionWithoutConfigTestBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Controller/DefaultController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Controller/DefaultController.php new file mode 100644 index 0000000000000..feaa957e77d46 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Controller/DefaultController.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\RoutingConditionServiceBundle\Controller; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; + +class DefaultController +{ + #[Route( + path: '/allowed/manually-tagged', + condition: 'service("manually_tagged").giveMeTrue()', + )] + public function allowedByManuallyTagged(): Response + { + return new Response(); + } + + #[Route( + path: '/allowed/auto-configured', + condition: 'service("auto_configured").flip(false)', + )] + public function allowedByAutoConfigured(): Response + { + return new Response(); + } + + #[Route( + path: '/allowed/auto-configured-non-aliased', + condition: 'service("auto_configured_non_aliased").alwaysTrue()', + )] + public function allowedByAutoConfiguredNonAliased(): Response + { + return new Response(); + } + + #[Route( + path: '/denied/manually-tagged', + condition: 'service("manually_tagged").giveMeFalse()', + )] + public function deniedByManuallyTagged(): Response + { + return new Response(); + } + + #[Route( + path: '/denied/auto-configured', + condition: 'service("auto_configured").flip(true)', + )] + public function deniedByAutoConfigured(): Response + { + return new Response(); + } + + #[Route( + path: '/denied/auto-configured-non-aliased', + condition: 'service("auto_configured_non_aliased").alwaysFalse()', + )] + public function deniedByAutoConfiguredNonAliased(): Response + { + return new Response(); + } + + #[Route( + path: '/denied/overridden', + condition: 'service("foo").isAllowed()', + )] + public function deniedByOverriddenAlias(): Response + { + return new Response(); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/RoutingConditionServiceBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/RoutingConditionServiceBundle.php new file mode 100644 index 0000000000000..8ab6891f12820 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/RoutingConditionServiceBundle.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\FrameworkBundle\Tests\Functional\Bundle\RoutingConditionServiceBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class RoutingConditionServiceBundle extends Bundle +{ +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/AutoConfiguredNonAliasedService.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/AutoConfiguredNonAliasedService.php new file mode 100644 index 0000000000000..a24df7ca90245 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/AutoConfiguredNonAliasedService.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\RoutingConditionServiceBundle\Service; + +use Symfony\Bundle\FrameworkBundle\Routing\Attribute\AsRoutingConditionService; + +#[AsRoutingConditionService] +class AutoConfiguredNonAliasedService +{ + public function alwaysFalse(): bool + { + return false; + } + + public function alwaysTrue(): bool + { + return true; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/AutoConfiguredService.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/AutoConfiguredService.php new file mode 100644 index 0000000000000..dbbd5eac25538 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/AutoConfiguredService.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\Bundle\RoutingConditionServiceBundle\Service; + +use Symfony\Bundle\FrameworkBundle\Routing\Attribute\AsRoutingConditionService; + +#[AsRoutingConditionService('auto_configured')] +class AutoConfiguredService +{ + public function flip(bool $value): bool + { + return !$value; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/FooOriginalService.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/FooOriginalService.php new file mode 100644 index 0000000000000..d78b332383308 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/FooOriginalService.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\Bundle\RoutingConditionServiceBundle\Service; + +use Symfony\Bundle\FrameworkBundle\Routing\Attribute\AsRoutingConditionService; + +#[AsRoutingConditionService('foo')] +class FooOriginalService +{ + public function isAllowed(): bool + { + return true; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/FooReplacementService.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/FooReplacementService.php new file mode 100644 index 0000000000000..5e69264c9dc70 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/FooReplacementService.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\Bundle\RoutingConditionServiceBundle\Service; + +use Symfony\Bundle\FrameworkBundle\Routing\Attribute\AsRoutingConditionService; + +#[AsRoutingConditionService(alias: 'foo', priority: -1)] +class FooReplacementService +{ + public function isAllowed(): bool + { + return false; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/ManuallyTaggedService.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/ManuallyTaggedService.php new file mode 100644 index 0000000000000..85f0feb027fec --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/RoutingConditionServiceBundle/Service/ManuallyTaggedService.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\RoutingConditionServiceBundle\Service; + +class ManuallyTaggedService +{ + public function giveMeTrue(): bool + { + return true; + } + + public function giveMeFalse(): bool + { + return false; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/UidController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/UidController.php new file mode 100644 index 0000000000000..2653f9fbcf82b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/UidController.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\UuidV1; + +class UidController +{ + #[Route(path: '/1/uuid-v1/{userId}')] + public function anyFormat(UuidV1 $userId): Response + { + return new Response($userId); + } + + #[Route(path: '/2/ulid/{id}', requirements: ['id' => '[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}'])] + public function specificFormatInAttribute(Ulid $id): Response + { + return new Response($id); + } + + #[Route(path: '/3/uuid-v1/{id<[0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz]{26}>}')] + public function specificFormatInPath(UuidV1 $id): Response + { + return new Response($id); + } + + #[Route(path: '/4/uuid-v1/{postId}/custom-uid/{commentId}')] + public function manyUids(UuidV1 $postId, TestCommentIdentifier $commentId): Response + { + return new Response($postId."\n".$commentId); + } +} + +class TestCommentIdentifier extends Ulid +{ +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php index fb70137ff7b22..9120032e11028 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php @@ -27,9 +27,7 @@ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('test'); - if ($this->customConfig) { - $this->customConfig->addConfiguration($treeBuilder->getRootNode()); - } + $this->customConfig?->addConfiguration($treeBuilder->getRootNode()); return $treeBuilder; } 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 8d41ce3267131..bfd37826b268b 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 @@ -60,3 +60,7 @@ array_controller: send_email: path: /send_email defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\EmailController::indexAction } + +uid: + resource: "../../Controller/UidController.php" + type: "annotation" diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/NonPublicService.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/NonPublicService.php index 5c9261ac77bba..284cfd073b242 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/NonPublicService.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/NonPublicService.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\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer; class NonPublicService diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PrivateService.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PrivateService.php index 6c7e05e532210..6a244cb40ce54 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PrivateService.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PrivateService.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\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer; class PrivateService diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PublicService.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PublicService.php index 1ea7b0b0ae180..14de890b630fe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PublicService.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PublicService.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\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer; class PublicService diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/UnusedPrivateService.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/UnusedPrivateService.php index 25a7244a50158..3becdba88b78b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/UnusedPrivateService.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/UnusedPrivateService.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\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer; class UnusedPrivateService diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php index b3885edaa3f36..b01295607b17f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php @@ -86,7 +86,6 @@ public function testClearFailed() $pool->save($item); $r = new \ReflectionObject($pool); $p = $r->getProperty('directory'); - $p->setAccessible(true); $poolDir = $p->getValue($pool); /** @var SplFileInfo $entry */ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/MailerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/MailerTest.php index ca371766deb22..942dd05178b24 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/MailerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/MailerTest.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\Bundle\FrameworkBundle\Tests\Functional; use Psr\Log\LoggerInterface; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RouterDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RouterDebugCommandTest.php index e9a8cd143b802..82e6bf8bb1505 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RouterDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RouterDebugCommandTest.php @@ -62,6 +62,23 @@ public function testSearchMultipleRoutes() $this->assertStringContainsString('/test', $tester->getDisplay()); } + public function testSearchMultipleRoutesWithoutInteraction() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(['name' => 'routerdebug'], ['interactive' => false]); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); + $this->assertStringNotContainsString('Select one of the matching routes:', $tester->getDisplay()); + $this->assertStringContainsString('routerdebug_session_welcome', $tester->getDisplay()); + $this->assertStringContainsString('/session', $tester->getDisplay()); + $this->assertStringContainsString('routerdebug_session_welcome_name', $tester->getDisplay()); + $this->assertStringContainsString('/session/{name} ', $tester->getDisplay()); + $this->assertStringContainsString('routerdebug_session_logout', $tester->getDisplay()); + $this->assertStringContainsString('/session_logout', $tester->getDisplay()); + $this->assertStringContainsString('routerdebug_test', $tester->getDisplay()); + $this->assertStringContainsString('/test', $tester->getDisplay()); + } + public function testSearchWithThrow() { $this->expectException(\InvalidArgumentException::class); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RoutingConditionServiceTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RoutingConditionServiceTest.php new file mode 100644 index 0000000000000..780060d7fc6f6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RoutingConditionServiceTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +class RoutingConditionServiceTest extends AbstractWebTestCase +{ + /** + * @dataProvider provideRoutes + */ + public function testCondition(int $code, string $path) + { + $client = static::createClient(['test_case' => 'RoutingConditionService']); + + $client->request('GET', $path); + $this->assertSame($code, $client->getResponse()->getStatusCode()); + } + + public function provideRoutes(): iterable + { + yield 'allowed by an autoconfigured service' => [ + 200, + '/allowed/manually-tagged', + ]; + + yield 'allowed by a manually tagged service' => [ + 200, + '/allowed/auto-configured', + ]; + + yield 'allowed by a manually tagged non aliased service' => [ + 200, + '/allowed/auto-configured-non-aliased', + ]; + + yield 'denied by an autoconfigured service' => [ + 404, + '/denied/manually-tagged', + ]; + + yield 'denied by a manually tagged service' => [ + 404, + '/denied/auto-configured', + ]; + + yield 'denied by a manually tagged non aliased service' => [ + 404, + '/denied/auto-configured-non-aliased', + ]; + + yield 'denied by an overridden service' => [ + 404, + '/denied/overridden', + ]; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/UidTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/UidTest.php new file mode 100644 index 0000000000000..61ba430b9b3ea --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/UidTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\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 + { + parent::setUp(); + + self::deleteTmpDir(); + } + + public function testArgumentValueResolverDisabled() + { + $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()); + } + + public function testArgumentValueResolverEnabled() + { + $client = $this->createClient(['test_case' => 'Uid', 'root_config' => 'config_enabled.yml']); + + // Any format + $client->request('GET', '/1/uuid-v1/'.$uuidV1 = new UuidV1()); + $this->assertSame((string) $uuidV1, $client->getResponse()->getContent()); + $client->request('GET', '/1/uuid-v1/'.$uuidV1->toBase58()); + $this->assertSame((string) $uuidV1, $client->getResponse()->getContent()); + $client->request('GET', '/1/uuid-v1/'.$uuidV1->toRfc4122()); + $this->assertSame((string) $uuidV1, $client->getResponse()->getContent()); + // Bad version + $client->request('GET', '/1/uuid-v1/'.$uuidV4 = new UuidV4()); + $this->assertSame(404, $client->getResponse()->getStatusCode()); + + // Only base58 format + $client->request('GET', '/2/ulid/'.($ulid = new Ulid())->toBase58()); + $this->assertSame((string) $ulid, $client->getResponse()->getContent()); + $client->request('GET', '/2/ulid/'.$ulid); + $this->assertSame(404, $client->getResponse()->getStatusCode()); + $client->request('GET', '/2/ulid/'.$ulid->toRfc4122()); + $this->assertSame(404, $client->getResponse()->getStatusCode()); + + // Only base32 format + $client->request('GET', '/3/uuid-v1/'.$uuidV1->toBase32()); + $this->assertSame((string) $uuidV1, $client->getResponse()->getContent()); + $client->request('GET', '/3/uuid-v1/'.$uuidV1); + $this->assertSame(404, $client->getResponse()->getStatusCode()); + $client->request('GET', '/3/uuid-v1/'.$uuidV1->toBase58()); + $this->assertSame(404, $client->getResponse()->getStatusCode()); + // Bad version + $client->request('GET', '/3/uuid-v1/'.(new UuidV6())->toBase32()); + $this->assertSame(404, $client->getResponse()->getStatusCode()); + + // Any format for both + $client->request('GET', '/4/uuid-v1/'.$uuidV1.'/custom-uid/'.$ulid->toRfc4122()); + $this->assertSame($uuidV1."\n".$ulid, $client->getResponse()->getContent()); + $client->request('GET', '/4/uuid-v1/'.$uuidV1->toBase58().'/custom-uid/'.$ulid->toBase58()); + $this->assertSame($uuidV1."\n".$ulid, $client->getResponse()->getContent()); + // Bad version + $client->request('GET', '/4/uuid-v1/'.$uuidV4.'/custom-uid/'.$ulid); + $this->assertSame(404, $client->getResponse()->getStatusCode()); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/bundles.php new file mode 100644 index 0000000000000..1fc9f4f2588eb --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/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\RoutingConditionServiceBundle\RoutingConditionServiceBundle; + +return [ + new FrameworkBundle(), + new RoutingConditionServiceBundle(), +]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/config.yml new file mode 100644 index 0000000000000..50cd22497fe20 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/config.yml @@ -0,0 +1,4 @@ +imports: + - { resource: ../config/default.yml } + - { resource: services_auto_configured.yml } + - { resource: services_manually_configured.yml } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/routing.yml new file mode 100644 index 0000000000000..221af8ca71770 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/routing.yml @@ -0,0 +1,4 @@ +_routingconditionservice_bundle: + prefix: / + resource: "@RoutingConditionServiceBundle/Controller" + type: annotation diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/services_auto_configured.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/services_auto_configured.yml new file mode 100644 index 0000000000000..4361226c754a1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/services_auto_configured.yml @@ -0,0 +1,13 @@ +services: + + _defaults: + autoconfigure: true + + Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\RoutingConditionServiceBundle\Service\AutoConfiguredService: + + auto_configured_non_aliased: + class: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\RoutingConditionServiceBundle\Service\AutoConfiguredNonAliasedService + + Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\RoutingConditionServiceBundle\Service\FooOriginalService: + + Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\RoutingConditionServiceBundle\Service\FooReplacementService: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/services_manually_configured.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/services_manually_configured.yml new file mode 100644 index 0000000000000..d7d8f28f760e7 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/RoutingConditionService/services_manually_configured.yml @@ -0,0 +1,8 @@ +services: + + _defaults: + autoconfigure: false + + Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\RoutingConditionServiceBundle\Service\ManuallyTaggedService: + tags: + - { name: 'routing.condition_service', alias: 'manually_tagged' } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/bundles.php new file mode 100644 index 0000000000000..15ff182c6fed5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/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/Uid/config_disabled.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/config_disabled.yml new file mode 100644 index 0000000000000..352c1451e350b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/config_disabled.yml @@ -0,0 +1,6 @@ +imports: + - { resource: "../config/default.yml" } + +framework: + uid: + enabled: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/config_enabled.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/config_enabled.yml new file mode 100644 index 0000000000000..f3139be1d19e5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/config_enabled.yml @@ -0,0 +1,5 @@ +imports: + - { resource: "../config/default.yml" } + +framework: + uid: ~ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/routing.yml new file mode 100644 index 0000000000000..46a625a0183cb --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/routing.yml @@ -0,0 +1,2 @@ +uid: + resource: "@TestBundle/Resources/config/routing.yml" diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php index d47ca5a822139..2a3cebb7fdf17 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php @@ -89,6 +89,11 @@ public function testFlexStyle() $response = $kernel->handle($request); $this->assertEquals('Have a great day!', $response->getContent()); + + $request = Request::create('/h'); + $response = $kernel->handle($request); + + $this->assertEquals('Have a great day!', $response->getContent()); } public function testSecretLoadedFromExtension() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/flex-style/config/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/flex-style/config/bundles.php index 0691b2b32d19c..fbde1ef1c9a87 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/flex-style/config/bundles.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/flex-style/config/bundles.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + use Symfony\Bundle\FrameworkBundle\FrameworkBundle; return [ 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 d4f2ce121be54..ecc693367d544 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 @@ -74,6 +74,7 @@ public function __destruct() protected function configureRoutes(RoutingConfigurator $routes): void { $routes->add('halloween', '/')->controller([$this, 'halloweenAction']); + $routes->add('halloween2', '/h')->controller($this->halloweenAction(...)); } protected function configureContainer(ContainerConfigurator $c): void diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php index f4bc970a5a84a..2b619243eaf11 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.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\Bundle\FrameworkBundle\Tests\Routing; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/DotenvVaultTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/DotenvVaultTest.php index 62ca08b07ca6b..780bffcff25a1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/DotenvVaultTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/DotenvVaultTest.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\Bundle\FrameworkBundle\Tests\Secrets; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php index de096ccb22de0..4beacafa0cb81 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.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\Bundle\FrameworkBundle\Tests\Secrets; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/TestBrowserTokenTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/TestBrowserTokenTest.php index 8c5387590a43a..cc308303474b7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/TestBrowserTokenTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/TestBrowserTokenTest.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\Bundle\FrameworkBundle\Tests\Test; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 46ff5344d06aa..8a44eff41ac86 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -16,18 +16,18 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "composer-runtime-api": ">=2.1", "ext-xml": "*", "symfony/cache": "^5.4|^6.0", "symfony/config": "^5.4|^6.0", "symfony/dependency-injection": "^5.4.5|^6.0.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/error-handler": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", + "symfony/http-kernel": "^6.1", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php81": "^1.22", "symfony/filesystem": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", "symfony/routing": "^5.4|^6.0" @@ -44,15 +44,17 @@ "symfony/polyfill-intl-icu": "~1.0", "symfony/form": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", + "symfony/html-sanitizer": "^6.1", "symfony/http-client": "^5.4|^6.0", "symfony/lock": "^5.4|^6.0", "symfony/mailer": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", + "symfony/messenger": "^6.1", "symfony/mime": "^5.4|^6.0", "symfony/notifier": "^5.4|^6.0", "symfony/process": "^5.4|^6.0", "symfony/rate-limiter": "^5.4|^6.0", "symfony/security-bundle": "^5.4|^6.0", + "symfony/semaphore": "^5.4|^6.0", "symfony/serializer": "^5.4|^6.0", "symfony/stopwatch": "^5.4|^6.0", "symfony/string": "^5.4|^6.0", @@ -62,6 +64,7 @@ "symfony/workflow": "^5.4|^6.0", "symfony/yaml": "^5.4|^6.0", "symfony/property-info": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", "symfony/web-link": "^5.4|^6.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "twig/twig": "^2.10|^3.0" @@ -84,7 +87,7 @@ "symfony/mime": "<5.4", "symfony/property-info": "<5.4", "symfony/property-access": "<5.4", - "symfony/serializer": "<5.4", + "symfony/serializer": "<6.1", "symfony/security-csrf": "<5.4", "symfony/security-core": "<5.4", "symfony/stopwatch": "<5.4", diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index ed389838e5e48..35631064bacf3 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +6.1 +--- + + * The `security.access_control` now accepts a `RequestMatcherInterface` under the `request_matcher` option as scope configuration + * Display the inherited roles of the logged-in user in the Web Debug Toolbar + 6.0 --- diff --git a/src/Symfony/Bundle/SecurityBundle/CacheWarmer/ExpressionCacheWarmer.php b/src/Symfony/Bundle/SecurityBundle/CacheWarmer/ExpressionCacheWarmer.php index a874e276feb5e..41036f81d31bb 100644 --- a/src/Symfony/Bundle/SecurityBundle/CacheWarmer/ExpressionCacheWarmer.php +++ b/src/Symfony/Bundle/SecurityBundle/CacheWarmer/ExpressionCacheWarmer.php @@ -18,7 +18,7 @@ class ExpressionCacheWarmer implements CacheWarmerInterface { private iterable $expressions; - private $expressionLanguage; + private ExpressionLanguage $expressionLanguage; /** * @param iterable $expressions diff --git a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php index d8778328092b3..ea1e44018ea05 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php @@ -33,8 +33,8 @@ final class DebugFirewallCommand extends Command { private array $firewallNames; - private $contexts; - private $eventDispatchers; + private ContainerInterface $contexts; + private ContainerInterface $eventDispatchers; private array $authenticators; /** @@ -242,7 +242,7 @@ private function formatCallable(mixed $callable): string if ($callable instanceof \Closure) { $r = new \ReflectionFunction($callable); - if (false !== strpos($r->name, '{closure}')) { + if (str_contains($r->name, '{closure}')) { return 'Closure()'; } if ($class = $r->getClosureScopeClass()) { diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index 0fdee87821861..c7df65486efdf 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -36,12 +36,12 @@ */ class SecurityDataCollector extends DataCollector implements LateDataCollectorInterface { - private $tokenStorage; - private $roleHierarchy; - private $logoutUrlGenerator; - private $accessDecisionManager; - private $firewallMap; - private $firewall; + private ?TokenStorageInterface $tokenStorage; + private ?RoleHierarchyInterface $roleHierarchy; + private ?LogoutUrlGenerator $logoutUrlGenerator; + private ?AccessDecisionManagerInterface $accessDecisionManager; + private ?FirewallMapInterface $firewallMap; + private ?TraceableFirewallListener $firewall; private bool $hasVarDumper; public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null, AccessDecisionManagerInterface $accessDecisionManager = null, FirewallMapInterface $firewallMap = null, TraceableFirewallListener $firewall = null) @@ -110,10 +110,8 @@ public function collect(Request $request, Response $response, \Throwable $except $logoutUrl = null; try { - if (null !== $this->logoutUrlGenerator) { - $logoutUrl = $this->logoutUrlGenerator->getLogoutPath(); - } - } catch (\Exception $e) { + $logoutUrl = $this->logoutUrlGenerator?->getLogoutPath(); + } catch (\Exception) { // fail silently when the logout URL cannot be generated } diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php index ed2f8e7144f02..357d2414d8bf2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\Debug; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; use Symfony\Component\VarDumper\Caster\ClassStub; @@ -21,7 +22,7 @@ */ trait TraceableListenerTrait { - private $response = null; + private ?Response $response = null; private mixed $listener; private ?float $time = null; private object $stub; @@ -44,7 +45,7 @@ public function getInfo(): array return [ 'response' => $this->response, 'time' => $this->time, - 'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener), + 'stub' => $this->stub ??= ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener), ]; } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php index ccab76679a32a..a0d6206134031 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener; use Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener; @@ -52,7 +53,7 @@ protected function registerLogoutHandler(ContainerBuilder $container) $csrfTokenStorage = $container->findDefinition('security.csrf.token_storage'); $csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass()); - if (!is_subclass_of($csrfTokenStorageClass, 'Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface')) { + if (!is_subclass_of($csrfTokenStorageClass, ClearableTokenStorageInterface::class)) { return; } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index e7dbdd42a7868..dec107d65dc18 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -134,6 +134,7 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode) ->fixXmlConfig('ip') ->fixXmlConfig('method') ->children() + ->scalarNode('request_matcher')->defaultNull()->end() ->scalarNode('requires_channel')->defaultNull()->end() ->scalarNode('path') ->defaultNull() @@ -255,7 +256,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto return $requiredBadge; } - if (false === strpos($requiredBadge, '\\')) { + if (!str_contains($requiredBadge, '\\')) { $fqcn = 'Symfony\Component\Security\Http\Authenticator\Passport\Badge\\'.$requiredBadge; if (class_exists($fqcn)) { return $fqcn; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index b2cd3fed196ba..92287664ece41 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -112,7 +112,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal foreach ($container->findTaggedServiceIds('security.remember_me_aware') as $serviceId => $attributes) { // register ContextListener - if ('security.context_listener' === substr($serviceId, 0, 25)) { + if (str_starts_with($serviceId, 'security.context_listener')) { continue; } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index aa3e246ad0e76..803fc5ed5d796 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -30,7 +30,9 @@ use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\RequestMatcher; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; @@ -43,6 +45,7 @@ use Symfony\Component\Security\Core\Authorization\Strategy\UnanimousStrategy; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\User\ChainUserProvider; +use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; use Symfony\Component\Security\Http\Event\CheckPassportEvent; @@ -195,14 +198,21 @@ private function createRoleHierarchy(array $config, ContainerBuilder $container) private function createAuthorization(array $config, ContainerBuilder $container) { foreach ($config['access_control'] as $access) { - $matcher = $this->createRequestMatcher( - $container, - $access['path'], - $access['host'], - $access['port'], - $access['methods'], - $access['ips'] - ); + if (isset($access['request_matcher'])) { + if ($access['path'] || $access['host'] || $access['port'] || $access['ips'] || $access['methods']) { + 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 { + $matcher = $this->createRequestMatcher( + $container, + $access['path'], + $access['host'], + $access['port'], + $access['methods'], + $access['ips'] + ); + } $attributes = $access['roles']; if ($access['allow_if']) { @@ -299,7 +309,7 @@ private function createFirewalls(array $config, ContainerBuilder $container) // register an autowire alias for the UserCheckerInterface if no custom user checker service is configured if (!$customUserChecker) { - $container->setAlias('Symfony\Component\Security\Core\User\UserCheckerInterface', new Alias('security.user_checker', false)); + $container->setAlias(UserCheckerInterface::class, new Alias('security.user_checker', false)); } } @@ -835,7 +845,7 @@ private function createExpression(ContainerBuilder $container, string $expressio } $container - ->register($id, 'Symfony\Component\ExpressionLanguage\Expression') + ->register($id, Expression::class) ->setPublic(false) ->addArgument($expression) ; @@ -874,7 +884,7 @@ private function createRequestMatcher(ContainerBuilder $container, string $path } $container - ->register($id, 'Symfony\Component\HttpFoundation\RequestMatcher') + ->register($id, RequestMatcher::class) ->setPublic(false) ->setArguments($arguments) ; diff --git a/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php b/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php index 414c5f12aec9f..98f54c8634abd 100644 --- a/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php +++ b/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php @@ -25,8 +25,8 @@ */ class FirewallListener extends Firewall { - private $map; - private $logoutUrlGenerator; + private FirewallMapInterface $map; + private LogoutUrlGenerator $logoutUrlGenerator; public function __construct(FirewallMapInterface $map, EventDispatcherInterface $dispatcher, LogoutUrlGenerator $logoutUrlGenerator) { diff --git a/src/Symfony/Bundle/SecurityBundle/EventListener/VoteListener.php b/src/Symfony/Bundle/SecurityBundle/EventListener/VoteListener.php index 1b37d92373705..ef715f5271a34 100644 --- a/src/Symfony/Bundle/SecurityBundle/EventListener/VoteListener.php +++ b/src/Symfony/Bundle/SecurityBundle/EventListener/VoteListener.php @@ -24,7 +24,7 @@ */ class VoteListener implements EventSubscriberInterface { - private $traceableAccessDecisionManager; + private TraceableAccessDecisionManager $traceableAccessDecisionManager; public function __construct(TraceableAccessDecisionManager $traceableAccessDecisionManager) { diff --git a/src/Symfony/Bundle/SecurityBundle/RememberMe/DecoratedRememberMeHandler.php b/src/Symfony/Bundle/SecurityBundle/RememberMe/DecoratedRememberMeHandler.php index a060fb5116ffb..56c2886c6c607 100644 --- a/src/Symfony/Bundle/SecurityBundle/RememberMe/DecoratedRememberMeHandler.php +++ b/src/Symfony/Bundle/SecurityBundle/RememberMe/DecoratedRememberMeHandler.php @@ -24,7 +24,7 @@ */ final class DecoratedRememberMeHandler implements RememberMeHandlerInterface { - private $handler; + private RememberMeHandlerInterface $handler; public function __construct(RememberMeHandlerInterface $handler) { diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/console.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/console.php index 5ab29caee1487..0fa0f9da0551a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/console.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/console.php @@ -16,10 +16,10 @@ return static function (ContainerConfigurator $container) { $container->services() ->set('security.command.user_password_hash', UserPasswordHashCommand::class) - ->args([ - service('security.password_hasher_factory'), - abstract_arg('list of user classes'), - ]) - ->tag('console.command') + ->args([ + service('security.password_hasher_factory'), + abstract_arg('list of user classes'), + ]) + ->tag('console.command') ; }; diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig index 26b59585a1c65..cf2b54e27cbb7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig @@ -5,7 +5,7 @@ {% block toolbar %} {% if collector.firewall %} {% set icon %} - {{ include('@Security/Collector/icon.svg') }} + {{ source('@Security/Collector/icon.svg') }} {{ collector.user|default('n/a') }} {% endset %} @@ -46,6 +46,26 @@ + {% if collector.supportsRoleHierarchy %} +
+ Inherited Roles + + {% if collector.inheritedRoles is empty %} + none + {% else %} + {% set remainingRoles = collector.inheritedRoles|slice(1) %} + {{ collector.inheritedRoles|first }} + {% if remainingRoles is not empty %} + + + + {{ remainingRoles|length }} more + + {% endif %} + {% endif %} + +
+ {% endif %} +
Token class {{ collector.tokenClass|abbr_class }} @@ -89,7 +109,7 @@ {% block menu %} - {{ include('@Security/Collector/icon.svg') }} + {{ source('@Security/Collector/icon.svg') }} Security {% endblock %} @@ -110,7 +130,7 @@
- {{ include('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }} + {{ source('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }} Authenticated
@@ -167,11 +187,11 @@ Name
- {{ include('@WebProfiler/Icon/' ~ (collector.firewall.security_enabled ? 'yes' : 'no') ~ '.svg') }} + {{ source('@WebProfiler/Icon/' ~ (collector.firewall.security_enabled ? 'yes' : 'no') ~ '.svg') }} Security enabled
- {{ include('@WebProfiler/Icon/' ~ (collector.firewall.stateless ? 'yes' : 'no') ~ '.svg') }} + {{ source('@WebProfiler/Icon/' ~ (collector.firewall.stateless ? 'yes' : 'no') ~ '.svg') }} Stateless
@@ -290,7 +310,7 @@ {{ profiler_dump(authenticator.stub) }} - {{ include('@WebProfiler/Icon/' ~ (authenticator.supports ? 'yes' : 'no') ~ '.svg') }} + {{ source('@WebProfiler/Icon/' ~ (authenticator.supports ? 'yes' : 'no') ~ '.svg') }} {{ '%0.2f'|format(authenticator.duration * 1000) }} ms {{ authenticator.passport ? profiler_dump(authenticator.passport) : '(none)' }} diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallAwareTrait.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallAwareTrait.php index d79d0b7a1df53..d422675377afa 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallAwareTrait.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallAwareTrait.php @@ -11,6 +11,9 @@ namespace Symfony\Bundle\SecurityBundle\Security; +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; + /** * Provides basic functionality for services mapped by the firewall name * in a container locator. @@ -21,9 +24,9 @@ */ trait FirewallAwareTrait { - private $locator; - private $requestStack; - private $firewallMap; + private ContainerInterface $locator; + private RequestStack $requestStack; + private FirewallMap $firewallMap; private function getForFirewall(): object { diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php index 3813420d28081..a81f6d8983113 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php @@ -23,9 +23,9 @@ class FirewallContext { private iterable $listeners; - private $exceptionListener; - private $logoutListener; - private $config; + private ?ExceptionListener $exceptionListener; + private ?LogoutListener $logoutListener; + private ?FirewallConfig $config; /** * @param iterable $listeners diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php index 4c8543df73fda..21e5b8aa68279 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php @@ -24,7 +24,7 @@ */ class FirewallMap implements FirewallMapInterface { - private $container; + private ContainerInterface $container; private iterable $map; public function __construct(ContainerInterface $container, iterable $map) diff --git a/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php index 9d8396a8830c9..23d96b6bbf479 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php @@ -25,7 +25,7 @@ */ class LazyFirewallContext extends FirewallContext { - private $tokenStorage; + private TokenStorage $tokenStorage; public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, TokenStorage $tokenStorage) { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php index ba1a1f8428c6b..ff4108d25cdc9 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php @@ -17,7 +17,7 @@ class AbstractFactoryTest extends TestCase { - private $container; + private ContainerBuilder $container; protected function setUp(): void { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 1e5f12537f8d1..bb74850729a58 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -26,6 +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\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; @@ -250,6 +251,80 @@ public function testRegisterRequestMatchersWithAllowIfExpression() ); } + public function testRegisterAccessControlWithSpecifiedRequestMatcherService() + { + $container = $this->getRawContainer(); + + $requestMatcherId = 'My\Test\RequestMatcher'; + $requestMatcher = new RequestMatcher('/'); + $container->set($requestMatcherId, $requestMatcher); + + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + 'providers' => [ + 'default' => ['id' => 'foo'], + ], + 'firewalls' => [ + 'some_firewall' => [ + 'pattern' => '/.*', + 'http_basic' => [], + ], + ], + 'access_control' => [ + ['request_matcher' => $requestMatcherId], + ], + ]); + + $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]; + $this->assertCount(3, $args); + $this->assertSame($requestMatcherId, (string) $args[0]); + } + + /** @dataProvider provideAdditionalRequestMatcherConstraints */ + public function testRegisterAccessControlWithRequestMatcherAndAdditionalOptionsThrowsInvalidException(array $additionalConstraints) + { + $container = $this->getRawContainer(); + + $requestMatcherId = 'My\Test\RequestMatcher'; + $requestMatcher = new RequestMatcher('/'); + $container->set($requestMatcherId, $requestMatcher); + + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + 'providers' => [ + 'default' => ['id' => 'foo'], + ], + 'firewalls' => [ + 'some_firewall' => [ + 'pattern' => '/.*', + 'http_basic' => [], + ], + ], + 'access_control' => [ + array_merge(['request_matcher' => $requestMatcherId], $additionalConstraints), + ], + ]); + + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('The "request_matcher" option should not be specified alongside other options. Consider integrating your constraints inside your RequestMatcher directly.'); + + $container->compile(); + } + + public function provideAdditionalRequestMatcherConstraints() + { + yield 'Invalid configuration with path' => [['path' => '^/url']]; + yield 'Invalid configuration with host' => [['host' => 'example.com']]; + yield 'Invalid configuration with port' => [['port' => 80]]; + yield 'Invalid configuration with methods' => [['methods' => ['POST']]]; + yield 'Invalid configuration with ips' => [['ips' => ['0.0.0.0']]]; + } + public function testRemovesExpressionCacheWarmerDefinitionIfNoExpressions() { $container = $this->getRawContainer(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php index f0558c5c5f5a6..dca54cfb0d999 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ApiAuthenticator.php @@ -40,7 +40,7 @@ public function supports(Request $request): ?bool public function authenticate(Request $request): Passport { $email = $request->headers->get('X-USER-EMAIL'); - if (false === strpos($email, '@')) { + if (!str_contains($email, '@')) { throw new BadCredentialsException('Email is not a valid email address.'); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/LoginLink/TestCustomLoginLinkSuccessHandler.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/LoginLink/TestCustomLoginLinkSuccessHandler.php index 0d1501508b58a..0cbc159ed4272 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/LoginLink/TestCustomLoginLinkSuccessHandler.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/LoginLink/TestCustomLoginLinkSuccessHandler.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\Bundle\SecurityBundle\Tests\Functional\Bundle\LoginLink; use Symfony\Component\HttpFoundation\JsonResponse; 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 a51702eec15b6..24ff10a4d0199 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 @@ -49,11 +49,9 @@ public function updateToken(string $series, string $tokenValue, \DateTime $lastU $token = $this->loadTokenBySeries($series); $refl = new \ReflectionClass($token); $tokenValueProp = $refl->getProperty('tokenValue'); - $tokenValueProp->setAccessible(true); $tokenValueProp->setValue($token, $tokenValue); $lastUsedProp = $refl->getProperty('lastUsed'); - $lastUsedProp->setAccessible(true); $lastUsedProp->setValue($token, $lastUsed); self::$db[$series] = $token; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RequestTrackerBundle/EventSubscriber/RequestTrackerSubscriber.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RequestTrackerBundle/EventSubscriber/RequestTrackerSubscriber.php index 9f442aeb11710..9496200e0486d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RequestTrackerBundle/EventSubscriber/RequestTrackerSubscriber.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/RequestTrackerBundle/EventSubscriber/RequestTrackerSubscriber.php @@ -17,7 +17,7 @@ final class RequestTrackerSubscriber implements EventSubscriberInterface { - private $lastRequest; + private ?Request $lastRequest; public static function getSubscribedEvents(): array { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Controller/AdminController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Controller/AdminController.php index db494ed936cbd..f9e73b8970657 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Controller/AdminController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Controller/AdminController.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\Bundle\SecurityBundle\Tests\Functional\Bundle\SecuredPageBundle\Controller; use Symfony\Component\DependencyInjection\ContainerAwareInterface; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/SecuredPageBundle.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/SecuredPageBundle.php index 3c3c96592abb8..92d743d2c754e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/SecuredPageBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/SecuredPageBundle.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\Bundle\SecurityBundle\Tests\Functional\Bundle\SecuredPageBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; 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 db9d39e7d6e74..68f896bcc68b1 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 @@ -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\Bundle\SecurityBundle\Tests\Functional\Bundle\SecuredPageBundle\Security\Core\User; use Symfony\Bundle\SecurityBundle\Tests\Functional\UserWithoutEquatable; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/RememberMeCookieTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/RememberMeCookieTest.php index aae5f9282e546..0bd2711f25ca0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/RememberMeCookieTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/RememberMeCookieTest.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\Bundle\SecurityBundle\Tests\Functional; use Symfony\Component\HttpFoundation\ResponseHeaderBag; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeCookie/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeCookie/bundles.php index 8d4a02497947a..9a26fb163a77d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeCookie/bundles.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeCookie/bundles.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\SecurityBundle\SecurityBundle; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/UserAuthenticatorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/UserAuthenticatorTest.php index ecb9cd8e7e33d..95e275e26e383 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Security/UserAuthenticatorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/UserAuthenticatorTest.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\Bundle\SecurityBundle\Tests\Security; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 9f928c2c4fb57..9c3db8d752dc6 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "composer-runtime-api": ">=2.1", "ext-xml": "*", "symfony/config": "^5.4|^6.0", diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md index 8bdf7748ff7d0..e082ba6501c36 100644 --- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.1 +--- + + * Add option `twig.file_name_pattern` to restrict which files are compiled by cache warmer and linter + 6.0 --- diff --git a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php index 1d88c5d73eb31..63d0227513845 100644 --- a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php +++ b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php @@ -24,8 +24,8 @@ */ class TemplateCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface { - private $container; - private $twig; + private ContainerInterface $container; + private Environment $twig; private iterable $iterator; public function __construct(ContainerInterface $container, iterable $iterator) @@ -53,7 +53,7 @@ public function warmUp(string $cacheDir): array if (\is_callable([$template, 'unwrap'])) { $files[] = (new \ReflectionClass($template->unwrap()))->getFileName(); } - } catch (Error $e) { + } catch (Error) { /* * Problem during compilation, give up for this template (e.g. syntax errors). * Failing silently here allows to ignore templates that rely on functions that aren't available in diff --git a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php index c9509c6582f82..5de2b2aad1358 100644 --- a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php +++ b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php @@ -13,7 +13,6 @@ use Symfony\Bridge\Twig\Command\LintCommand as BaseLintCommand; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Finder\Finder; /** * Command that will validate your template syntax and output encountered errors. @@ -47,9 +46,7 @@ protected function configure() protected function findFiles(string $filename): iterable { if (str_starts_with($filename, '@')) { - $dir = $this->getApplication()->getKernel()->locateResource($filename); - - return Finder::create()->files()->in($dir)->name('*.twig'); + $filename = $this->getApplication()->getKernel()->locateResource($filename); } return parent::findFiles($filename); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index df688c5dd43ee..83654fb4813e0 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -141,6 +141,15 @@ private function addTwigOptions(ArrayNodeDefinition $rootNode) ->info('The default path used to load templates') ->defaultValue('%kernel.project_dir%/templates') ->end() + ->arrayNode('file_name_pattern') + ->example('*.twig') + ->info('Pattern of file name used for cache warmer and linter') + ->beforeNormalization() + ->ifString() + ->then(function ($value) { return [$value]; }) + ->end() + ->prototype('scalar')->end() + ->end() ->arrayNode('paths') ->normalizeKeys(false) ->useAttributeAsKey('paths') diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php index 778d19f523942..7505c1fcd36fa 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configurator/EnvironmentConfigurator.php @@ -13,6 +13,7 @@ use Symfony\Bridge\Twig\UndefinedCallableHandler; use Twig\Environment; +use Twig\Extension\CoreExtension; // BC/FC with namespaced Twig class_exists(Environment::class); @@ -43,13 +44,13 @@ public function __construct(string $dateFormat, string $intervalFormat, ?string public function configure(Environment $environment) { - $environment->getExtension('Twig\Extension\CoreExtension')->setDateFormat($this->dateFormat, $this->intervalFormat); + $environment->getExtension(CoreExtension::class)->setDateFormat($this->dateFormat, $this->intervalFormat); if (null !== $this->timezone) { - $environment->getExtension('Twig\Extension\CoreExtension')->setTimezone($this->timezone); + $environment->getExtension(CoreExtension::class)->setTimezone($this->timezone); } - $environment->getExtension('Twig\Extension\CoreExtension')->setNumberFormat($this->decimals, $this->decimalPoint, $this->thousandsSeparator); + $environment->getExtension(CoreExtension::class)->setNumberFormat($this->decimals, $this->decimalPoint, $this->thousandsSeparator); // wrap UndefinedCallableHandler in closures for lazy-autoloading $environment->registerUndefinedFilterCallback(function ($name) { return UndefinedCallableHandler::onUndefinedFilter($name); }); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index 2fb47d3746aef..f598524da4d64 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -17,10 +17,12 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Form\AbstractRendererEngine; use Symfony\Component\Form\Form; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Translation\Translator; +use Symfony\Contracts\Service\ResetInterface; use Twig\Extension\ExtensionInterface; use Twig\Extension\RuntimeExtensionInterface; use Twig\Loader\LoaderInterface; @@ -40,6 +42,12 @@ public function load(array $configs, ContainerBuilder $container) if ($container::willBeAvailable('symfony/form', Form::class, ['symfony/twig-bundle'])) { $loader->load('form.php'); + + if (is_subclass_of(AbstractRendererEngine::class, ResetInterface::class)) { + $container->getDefinition('twig.form.engine')->addTag('kernel.reset', [ + 'method' => 'reset', + ]); + } } if ($container::willBeAvailable('symfony/console', Application::class, ['symfony/twig-bundle'])) { @@ -97,6 +105,12 @@ public function load(array $configs, ContainerBuilder $container) // paths are modified in ExtensionPass if forms are enabled $container->getDefinition('twig.template_iterator')->replaceArgument(1, $config['paths']); + $container->getDefinition('twig.template_iterator')->replaceArgument(3, $config['file_name_pattern']); + + if ($container->hasDefinition('twig.command.lint')) { + $container->getDefinition('twig.command.lint')->replaceArgument(1, $config['file_name_pattern'] ?: ['*.twig']); + } + foreach ($this->getBundleTemplatePaths($container, $config) as $name => $paths) { $namespace = $this->normalizeBundleName($name); foreach ($paths as $path) { diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/console.php b/src/Symfony/Bundle/TwigBundle/Resources/config/console.php index 0dc7ebdb7a5ad..b0303c3c24f09 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/console.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/console.php @@ -27,7 +27,7 @@ ->tag('console.command') ->set('twig.command.lint', LintCommand::class) - ->args([service('twig')]) + ->args([service('twig'), abstract_arg('File name pattern')]) ->tag('console.command') ; }; diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php index 2380d130b741b..6a5e52df6b472 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php @@ -75,7 +75,7 @@ ->call('setRequestStack', [service('request_stack')->ignoreOnInvalid()]) ->set('twig.template_iterator', TemplateIterator::class) - ->args([service('kernel'), abstract_arg('Twig paths'), param('twig.default_path')]) + ->args([service('kernel'), abstract_arg('Twig paths'), param('twig.default_path'), abstract_arg('File name pattern')]) ->set('twig.template_cache_warmer', TemplateCacheWarmer::class) ->args([service(ContainerInterface::class), service('twig.template_iterator')]) diff --git a/src/Symfony/Bundle/TwigBundle/TemplateIterator.php b/src/Symfony/Bundle/TwigBundle/TemplateIterator.php index d001cbcc74ad7..7242cfb0c5d44 100644 --- a/src/Symfony/Bundle/TwigBundle/TemplateIterator.php +++ b/src/Symfony/Bundle/TwigBundle/TemplateIterator.php @@ -25,20 +25,23 @@ */ class TemplateIterator implements \IteratorAggregate { - private $kernel; + private KernelInterface $kernel; private \Traversable $templates; private array $paths; private ?string $defaultPath; + private array $namePatterns; /** - * @param array $paths Additional Twig paths to warm - * @param string|null $defaultPath The directory where global templates can be stored + * @param array $paths Additional Twig paths to warm + * @param string|null $defaultPath The directory where global templates can be stored + * @param string[] $namePatterns Pattern of file names */ - public function __construct(KernelInterface $kernel, array $paths = [], string $defaultPath = null) + public function __construct(KernelInterface $kernel, array $paths = [], string $defaultPath = null, array $namePatterns = []) { $this->kernel = $kernel; $this->paths = $paths; $this->defaultPath = $defaultPath; + $this->namePatterns = $namePatterns; } public function getIterator(): \Traversable @@ -82,7 +85,7 @@ private function findTemplatesInDirectory(string $dir, string $namespace = null, } $templates = []; - foreach (Finder::create()->files()->followLinks()->in($dir)->exclude($excludeDirs) as $file) { + foreach (Finder::create()->files()->followLinks()->in($dir)->exclude($excludeDirs)->name($this->namePatterns) as $file) { $templates[] = (null !== $namespace ? '@'.$namespace.'/' : '').str_replace('\\', '/', $file->getRelativePathname()); } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index 1cc9c0ebf3e5c..361e0b8b24d0c 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -225,7 +225,6 @@ public function testStopwatchExtensionAvailability($debug, $stopwatchEnabled, $e $tokenParsers = $container->get('test.twig.extension.debug.stopwatch')->getTokenParsers(); $stopwatchIsAvailable = new \ReflectionProperty($tokenParsers[0], 'stopwatchIsAvailable'); - $stopwatchIsAvailable->setAccessible(true); $this->assertSame($expected, $stopwatchIsAvailable->getValue($tokenParsers[0])); } @@ -297,19 +296,11 @@ private function loadFromFile(ContainerBuilder $container, $file, $format) { $locator = new FileLocator(__DIR__.'/Fixtures/'.$format); - switch ($format) { - case 'php': - $loader = new PhpFileLoader($container, $locator); - break; - case 'xml': - $loader = new XmlFileLoader($container, $locator); - break; - case 'yml': - $loader = new YamlFileLoader($container, $locator); - break; - default: - throw new \InvalidArgumentException(sprintf('Unsupported format: "%s"', $format)); - } + $loader = match ($format) { + 'php' => new PhpFileLoader($container, $locator), + 'xml' => new XmlFileLoader($container, $locator), + 'yml' => new YamlFileLoader($container, $locator), + }; $loader->load($file.'.'.$format); } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Fixtures/templates/Foo/not-twig.js b/src/Symfony/Bundle/TwigBundle/Tests/Fixtures/templates/Foo/not-twig.js new file mode 100644 index 0000000000000..ee120443f880d --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/Fixtures/templates/Foo/not-twig.js @@ -0,0 +1 @@ +/* Not Twig template */ diff --git a/src/Symfony/Bundle/TwigBundle/Tests/TemplateIteratorTest.php b/src/Symfony/Bundle/TwigBundle/Tests/TemplateIteratorTest.php index 0c6b02a707270..2ca3d80ae4e8c 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/TemplateIteratorTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/TemplateIteratorTest.php @@ -19,15 +19,25 @@ class TemplateIteratorTest extends TestCase { public function testGetIterator() { - $bundle = $this->createMock(BundleInterface::class); - $bundle->expects($this->any())->method('getName')->willReturn('BarBundle'); - $bundle->expects($this->any())->method('getPath')->willReturn(__DIR__.'/Fixtures/templates/BarBundle'); + $iterator = new TemplateIterator($this->createKernelMock(), [__DIR__.'/Fixtures/templates/Foo' => 'Foo'], __DIR__.'/DependencyInjection/Fixtures/templates'); - $kernel = $this->createMock(Kernel::class); - $kernel->expects($this->any())->method('getBundles')->willReturn([ - $bundle, - ]); - $iterator = new TemplateIterator($kernel, [__DIR__.'/Fixtures/templates/Foo' => 'Foo'], __DIR__.'/DependencyInjection/Fixtures/templates'); + $sorted = iterator_to_array($iterator); + sort($sorted); + $this->assertEquals( + [ + '@Bar/index.html.twig', + '@Bar/layout.html.twig', + '@Foo/index.html.twig', + '@Foo/not-twig.js', + 'layout.html.twig', + ], + $sorted + ); + } + + public function testGetIteratorWithFileNameFilter() + { + $iterator = new TemplateIterator($this->createKernelMock(), [__DIR__.'/Fixtures/templates/Foo' => 'Foo'], __DIR__.'/DependencyInjection/Fixtures/templates', ['*.twig']); $sorted = iterator_to_array($iterator); sort($sorted); @@ -41,4 +51,18 @@ public function testGetIterator() $sorted ); } + + private function createKernelMock(): Kernel + { + $bundle = $this->createMock(BundleInterface::class); + $bundle->expects($this->any())->method('getName')->willReturn('BarBundle'); + $bundle->expects($this->any())->method('getPath')->willReturn(__DIR__.'/Fixtures/templates/BarBundle'); + + $kernel = $this->createMock(Kernel::class); + $kernel->expects($this->any())->method('getBundles')->willReturn([ + $bundle, + ]); + + return $kernel; + } } diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 9812d62afb0a9..379d95c2fd3cb 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -16,9 +16,10 @@ } ], "require": { - "php": ">=8.0.2", + "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/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^5.4|^6.0", @@ -28,7 +29,6 @@ "require-dev": { "symfony/asset": "^5.4|^6.0", "symfony/stopwatch": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", "symfony/form": "^5.4|^6.0", @@ -40,7 +40,6 @@ "doctrine/annotations": "^1.10.4" }, "conflict": { - "symfony/dependency-injection": "<5.4", "symfony/framework-bundle": "<5.4", "symfony/translation": "<5.4" }, diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index f0974a6ed9f1a..7b4a3d7362852 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.1 +--- + + * Add a download link in mailer profiler for email attachments + 5.4 --- diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index 9a08dc9f6d235..3cdec828217b5 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -71,9 +71,7 @@ public function panelAction(Request $request, string $token): Response { $this->denyAccessIfProfilerDisabled(); - if (null !== $this->cspHandler) { - $this->cspHandler->disableCsp(); - } + $this->cspHandler?->disableCsp(); $panel = $request->query->get('panel'); $page = $request->query->get('page', 'home'); @@ -148,7 +146,7 @@ public function toolbarAction(Request $request, string $token = null): Response $url = null; try { $url = $this->generator->generate('_profiler', ['token' => $token], UrlGeneratorInterface::ABSOLUTE_URL); - } catch (\Exception $e) { + } catch (\Exception) { // the profiler is not enabled } @@ -172,9 +170,7 @@ public function searchBarAction(Request $request): Response { $this->denyAccessIfProfilerDisabled(); - if (null !== $this->cspHandler) { - $this->cspHandler->disableCsp(); - } + $this->cspHandler?->disableCsp(); if (!$request->hasSession()) { $ip = @@ -224,9 +220,7 @@ public function searchResultsAction(Request $request, string $token): Response { $this->denyAccessIfProfilerDisabled(); - if (null !== $this->cspHandler) { - $this->cspHandler->disableCsp(); - } + $this->cspHandler?->disableCsp(); $profile = $this->profiler->loadProfile($token); @@ -312,9 +306,7 @@ public function phpinfoAction(): Response { $this->denyAccessIfProfilerDisabled(); - if (null !== $this->cspHandler) { - $this->cspHandler->disableCsp(); - } + $this->cspHandler?->disableCsp(); ob_start(); phpinfo(); @@ -323,6 +315,28 @@ public function phpinfoAction(): Response return new Response($phpinfo, 200, ['Content-Type' => 'text/html']); } + /** + * Displays the Xdebug info. + * + * @throws NotFoundHttpException + */ + public function xdebugAction(): Response + { + $this->denyAccessIfProfilerDisabled(); + + if (!\function_exists('xdebug_info')) { + throw new NotFoundHttpException('Xdebug must be installed in version 3.'); + } + + $this->cspHandler?->disableCsp(); + + ob_start(); + xdebug_info(); + $xdebugInfo = ob_get_clean(); + + return new Response($xdebugInfo, 200, ['Content-Type' => 'text/html']); + } + /** * Displays the source of a file. * @@ -334,9 +348,7 @@ public function openAction(Request $request): Response throw new NotFoundHttpException('The base dir should be set.'); } - if ($this->profiler) { - $this->profiler->disable(); - } + $this->profiler?->disable(); $file = $request->query->get('file'); $line = $request->query->get('line'); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php b/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php index ce241369265fe..3275ea792672b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php @@ -224,7 +224,7 @@ private function hasHashOrNonce(array $directives): bool if (!str_ends_with($directive, '\'')) { continue; } - if ('\'nonce-' === substr($directive, 0, 7)) { + if (str_starts_with($directive, '\'nonce-')) { return true; } if (\in_array(substr($directive, 0, 8), ['\'sha256-', '\'sha384-', '\'sha512-'], true)) { diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index 55f8565b20db5..5152223669c8d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -40,13 +40,13 @@ class WebDebugToolbarListener implements EventSubscriberInterface public const DISABLED = 1; public const ENABLED = 2; - private $twig; - private $urlGenerator; + private Environment $twig; + private ?UrlGeneratorInterface $urlGenerator; private bool $interceptRedirects; private int $mode; private string $excludedAjaxPaths; - private $cspHandler; - private $dumpDataCollector; + private ?ContentSecurityPolicyHandler $cspHandler; + private ?DumpDataCollector $dumpDataCollector; public function __construct(Environment $twig, bool $interceptRedirects = false, int $mode = self::ENABLED, UrlGeneratorInterface $urlGenerator = null, string $excludedAjaxPaths = '^/bundles|^/_wdt', ContentSecurityPolicyHandler $cspHandler = null, DumpDataCollector $dumpDataCollector = null) { @@ -95,7 +95,7 @@ public function onKernelResponse(ResponseEvent $event) $nonces = []; if ($this->cspHandler) { - if ($this->dumpDataCollector && $this->dumpDataCollector->getDumpsCount() > 0) { + if ($this->dumpDataCollector?->getDumpsCount() > 0) { $this->cspHandler->disableCsp(); } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml index f20cba0e673f9..1f3bbe0b61620 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml @@ -20,6 +20,10 @@ web_profiler.controller.profiler::phpinfoAction + + web_profiler.controller.profiler::xdebugAction + + web_profiler.controller.profiler::searchResultsAction diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/ajax.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/ajax.html.twig index e4e7d6418e24c..3e89707507ffc 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/ajax.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/ajax.html.twig @@ -2,7 +2,7 @@ {% block toolbar %} {% set icon %} - {{ include('@WebProfiler/Icon/ajax.svg') }} + {{ source('@WebProfiler/Icon/ajax.svg') }} 0 {% endset %} 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 0c406e9442c1e..b2f06be8c5f3b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/cache.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/cache.html.twig @@ -3,7 +3,7 @@ {% block toolbar %} {% if collector.totals.calls > 0 %} {% set icon %} - {{ include('@WebProfiler/Icon/cache.svg') }} + {{ source('@WebProfiler/Icon/cache.svg') }} {{ collector.totals.calls }} in @@ -37,7 +37,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/cache.svg') }} + {{ source('@WebProfiler/Icon/cache.svg') }} Cache 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 6dfd27bcbc67a..5f8cf89b2225e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig @@ -20,7 +20,7 @@ {% set icon %} - {{ include('@WebProfiler/Icon/symfony.svg') }} + {{ source('@WebProfiler/Icon/symfony.svg') }} {{ collector.symfonyState is defined ? collector.symfonyversion : 'n/a' }} {% endset %} @@ -64,7 +64,13 @@
PHP Extensions - xdebug {{ collector.hasxdebug ? '✓' : '✗' }} + {% if collector.hasXdebugInfo %} + + {% endif %} + Xdebug {{ collector.hasXdebug ? '✓' : '✗' }} + {% if collector.hasXdebugInfo %} + + {% endif %} APCu {{ collector.hasapcu ? '✓' : '✗' }} OPcache {{ collector.haszendopcache ? '✓' : '✗' }}
@@ -102,7 +108,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/config.svg') }} + {{ source('@WebProfiler/Icon/config.svg') }} Configuration {% endblock %} @@ -185,17 +191,17 @@
- {{ include('@WebProfiler/Icon/' ~ (collector.haszendopcache ? 'yes' : 'no') ~ '.svg') }} + {{ source('@WebProfiler/Icon/' ~ (collector.haszendopcache ? 'yes' : 'no') ~ '.svg') }} OPcache
- {{ include('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no-gray') ~ '.svg') }} + {{ source('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no-gray') ~ '.svg') }} APCu
- {{ include('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no-gray') ~ '.svg') }} + {{ source('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no-gray') ~ '.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 c0be48a377032..af4f8b5e73ef0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig @@ -4,7 +4,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/event.svg') }} + {{ source('@WebProfiler/Icon/event.svg') }} Events {% endblock %} 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 1fe0f5d470723..e27200d7abdf7 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig @@ -12,7 +12,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/exception.svg') }} + {{ source('@WebProfiler/Icon/exception.svg') }} Exception {% if collector.hasexception %} 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 db97100e49b37..c568d894f3053 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -6,7 +6,7 @@ {% if collector.data.nb_errors > 0 or collector.data.forms|length %} {% set status_color = collector.data.nb_errors ? 'red' %} {% set icon %} - {{ include('@WebProfiler/Icon/form.svg') }} + {{ source('@WebProfiler/Icon/form.svg') }} {{ collector.data.nb_errors ?: collector.data.forms|length }} @@ -29,7 +29,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/form.svg') }} + {{ source('@WebProfiler/Icon/form.svg') }} Forms {% if collector.data.nb_errors > 0 %} 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 8496ef186dfd6..b6d925dc3ba27 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 @@ -3,7 +3,7 @@ {% block toolbar %} {% if collector.requestCount %} {% set icon %} - {{ include('@WebProfiler/Icon/http-client.svg') }} + {{ source('@WebProfiler/Icon/http-client.svg') }} {% set status_color = '' %} {{ collector.requestCount }} {% endset %} @@ -25,7 +25,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/http-client.svg') }} + {{ source('@WebProfiler/Icon/http-client.svg') }} HTTP Client {% if collector.requestCount %} @@ -94,6 +94,11 @@ Profile {% endif %} + {% if trace.curlCommand is not null %} + + + + {% endif %} @@ -110,7 +115,7 @@ {{ 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 df2679d79c9ee..5246dc1049f0f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -6,7 +6,7 @@ {% if collector.counterrors or collector.countdeprecations or collector.countwarnings %} {% set icon %} {% set status_color = collector.counterrors ? 'red' : collector.countwarnings ? 'yellow' : 'none' %} - {{ include('@WebProfiler/Icon/logger.svg') }} + {{ source('@WebProfiler/Icon/logger.svg') }} {{ collector.counterrors ?: (collector.countdeprecations + collector.countwarnings) }} {% endset %} @@ -33,7 +33,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/logger.svg') }} + {{ source('@WebProfiler/Icon/logger.svg') }} Logs {% if collector.counterrors or collector.countdeprecations or collector.countwarnings %} @@ -83,7 +83,7 @@
- {{ include('@WebProfiler/Icon/filter.svg') }} + {{ source('@WebProfiler/Icon/filter.svg') }} Level ({{ filters.priority|length - 1 }}) @@ -104,7 +104,7 @@
- {{ include('@WebProfiler/Icon/filter.svg') }} + {{ source('@WebProfiler/Icon/filter.svg') }} Channel ({{ filters.channel|length - 1 }}) 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 dab2e9c6c0c67..1b898ad5397ec 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig @@ -5,7 +5,7 @@ {% if events.messages|length %} {% set icon %} - {% include('@WebProfiler/Icon/mailer.svg') %} + {{ source('@WebProfiler/Icon/mailer.svg') }} {{ events.messages|length }} {% endset %} @@ -65,7 +65,7 @@ {% set events = collector.events %} - {{ include('@WebProfiler/Icon/mailer.svg') }} + {{ source('@WebProfiler/Icon/mailer.svg') }} E-mails {% if events.messages|length > 0 %} @@ -129,6 +129,13 @@ 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 @@ -187,6 +194,15 @@ diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig index 1336a57a23398..4688272ef27ad 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig @@ -3,7 +3,7 @@ {% block toolbar %} {% set icon %} {% set status_color = (collector.memory / 1024 / 1024) > 50 ? 'yellow' %} - {{ include('@WebProfiler/Icon/memory.svg') }} + {{ source('@WebProfiler/Icon/memory.svg') }} {{ '%.1f'|format(collector.memory / 1024 / 1024) }} MiB {% endset %} 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 b48aaa82e5787..0d0f11457f408 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig @@ -6,7 +6,7 @@ {% if collector.messages|length > 0 %} {% set status_color = collector.exceptionsCount ? 'red' %} {% set icon %} - {{ include('@WebProfiler/Icon/messenger.svg') }} + {{ source('@WebProfiler/Icon/messenger.svg') }} {{ collector.messages|length }} {% endset %} @@ -31,7 +31,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/messenger.svg') }} + {{ source('@WebProfiler/Icon/messenger.svg') }} Messages {% if collector.exceptionsCount > 0 %} @@ -119,8 +119,8 @@ exception {% endif %} - {{ include('@WebProfiler/images/icon-minus-square.svg') }} - {{ include('@WebProfiler/images/icon-plus-square.svg') }} + {{ source('@WebProfiler/images/icon-minus-square.svg') }} + {{ source('@WebProfiler/images/icon-plus-square.svg') }} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig index dd17fab989a6b..5c77585d489a9 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/notifier.html.twig @@ -5,7 +5,7 @@ {% if events.messages|length %} {% set icon %} - {% include('@WebProfiler/Icon/notifier.svg') %} + {{ source('@WebProfiler/Icon/notifier.svg') }} {{ events.messages|length }} {% endset %} @@ -68,7 +68,7 @@ {% set events = collector.events %} - {{ include('@WebProfiler/Icon/notifier.svg') }} + {{ source('@WebProfiler/Icon/notifier.svg') }} Notifications {% if events.messages|length > 0 %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig index 18311c169fece..ec15223b0bc26 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig @@ -24,8 +24,8 @@ {% set icon %} {{ collector.statuscode }} {% if collector.route %} - {% if collector.redirect %}{{ include('@WebProfiler/Icon/redirect.svg') }}{% endif %} - {% if collector.forwardtoken %}{{ include('@WebProfiler/Icon/forward.svg') }}{% endif %} + {% if collector.redirect %}{{ source('@WebProfiler/Icon/redirect.svg') }}{% endif %} + {% if collector.forwardtoken %}{{ source('@WebProfiler/Icon/forward.svg') }}{% endif %} {{ 'GET' != collector.method ? collector.method }} @ {{ collector.route }} {% endif %} @@ -99,7 +99,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/request.svg') }} + {{ source('@WebProfiler/Icon/request.svg') }} Request / Response {% endblock %} @@ -265,7 +265,7 @@
- {{ include('@WebProfiler/Icon/' ~ (collector.statelesscheck ? 'yes' : 'no') ~ '.svg') }} + {{ source('@WebProfiler/Icon/' ~ (collector.statelesscheck ? 'yes' : 'no') ~ '.svg') }} Stateless check enabled
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/router.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/router.html.twig index a1449c2b272b2..f8f8dd8733a46 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/router.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/router.html.twig @@ -4,7 +4,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/router.svg') }} + {{ source('@WebProfiler/Icon/router.svg') }} Routing {% endblock %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig new file mode 100644 index 0000000000000..c9197742f065a --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/serializer.html.twig @@ -0,0 +1,228 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% import _self as helper %} + +{% block menu %} + + {{ include('@WebProfiler/Icon/validator.svg') }} + Serializer + +{% endblock %} + +{% block panel %} +

Serializer

+ {% if not collector.handledCount %} +
+

Nothing was handled by the serializer for this request.

+
+ {% else %} +
+
+ {{ collector.handledCount }} + Handled +
+ +
+ {{ '%.2f'|format(collector.totalTime * 1000) }} ms + Total time +
+
+ +
+ {{ helper.render_serialize_tab(collector.data, true) }} + {{ helper.render_serialize_tab(collector.data, false) }} + + {{ helper.render_normalize_tab(collector.data, true) }} + {{ helper.render_normalize_tab(collector.data, false) }} + + {{ helper.render_encode_tab(collector.data, true) }} + {{ helper.render_encode_tab(collector.data, false) }} +
+ {% endif %} +{% endblock %} + +{% macro render_serialize_tab(collectorData, serialize) %} + {% set data = serialize ? collectorData.serialize : collectorData.deserialize %} + {% set cellPrefix = serialize ? 'serialize' : 'deserialize' %} + +
+

{{ serialize ? 'serialize' : 'deserialize' }} {{ data|length }}

+
+ {% if not data|length %} +
+

Nothing was {{ serialize ? 'serialized' : 'deserialized' }}.

+
+ {% else %} + + + + + + + + + + + + {% for item in data %} + + + + + + + + {% endfor %} + +
DataContextNormalizerEncoderTime
{{ helper.render_data_cell(item, loop.index, cellPrefix) }}{{ helper.render_context_cell(item, loop.index, cellPrefix) }}{{ helper.render_normalizer_cell(item, loop.index, cellPrefix) }}{{ helper.render_encoder_cell(item, loop.index, cellPrefix) }}{{ helper.render_time_cell(item) }}
+ {% endif %} +
+
+{% endmacro %} + +{% macro render_normalize_tab(collectorData, normalize) %} + {% set data = normalize ? collectorData.normalize : collectorData.denormalize %} + {% set cellPrefix = normalize ? 'normalize' : 'denormalize' %} + +
+

{{ normalize ? 'normalize' : 'denormalize' }} {{ data|length }}

+
+ {% if not data|length %} +
+

Nothing was {{ normalize ? 'normalized' : 'denormalized' }}.

+
+ {% else %} + + + + + + + + + + + {% for item in data %} + + + + + + + {% endfor %} + +
DataContextNormalizerTime
{{ helper.render_data_cell(item, loop.index, cellPrefix) }}{{ helper.render_context_cell(item, loop.index, cellPrefix) }}{{ helper.render_normalizer_cell(item, loop.index, cellPrefix) }}{{ helper.render_time_cell(item) }}
+ {% endif %} +
+
+{% endmacro %} + +{% macro render_encode_tab(collectorData, encode) %} + {% set data = encode ? collectorData.encode : collectorData.decode %} + {% set cellPrefix = encode ? 'encode' : 'decode' %} + +
+

{{ encode ? 'encode' : 'decode' }} {{ data|length }}

+
+ {% if not data|length %} +
+

Nothing was {{ encode ? 'encoded' : 'decoded' }}.

+
+ {% else %} + + + + + + + + + + + {% for item in data %} + + + + + + + {% endfor %} + +
DataContextEncoderTime
{{ helper.render_data_cell(item, loop.index, cellPrefix) }}{{ helper.render_context_cell(item, loop.index, cellPrefix) }}{{ helper.render_encoder_cell(item, loop.index, cellPrefix) }}{{ helper.render_time_cell(item) }}
+ {% endif %} +
+
+{% endmacro %} + +{% macro render_data_cell(item, index, method) %} + {% set data_id = 'data-' ~ method ~ '-' ~ index %} + + {{ item.dataType }} + +
+ Show contents +
+ {{ profiler_dump(item.data) }} +
+
+{% endmacro %} + +{% macro render_context_cell(item, index, method) %} + {% set context_id = 'context-' ~ method ~ '-' ~ index %} + + {% if item.type %} + Type: {{ item.type }} +
Format: {{ item.format ? item.format : 'none' }}
+ {% else %} + Format: {{ item.format ? item.format : 'none' }} + {% endif %} + +
+ Show context +
+ {{ profiler_dump(item.context) }} +
+
+{% endmacro %} + +{% macro render_normalizer_cell(item, index, method) %} + {% set nested_normalizers_id = 'nested-normalizers-' ~ method ~ '-' ~ index %} + + {{ item.normalizer.class }} ({{ '%.2f'|format(item.normalizer.time * 1000) }} ms) + + {% if item.normalization|length > 1 %} +
+ Show nested normalizers +
+
    + {% for normalizer in item.normalization %} +
  • x{{ normalizer.calls }} {{ normalizer.class }} ({{ '%.2f'|format(normalizer.time * 1000) }} ms)
  • + {% endfor %} +
+
+
+ {% endif %} +{% endmacro %} + +{% macro render_encoder_cell(item, index, method) %} + {% set nested_encoders_id = 'nested-encoders-' ~ method ~ '-' ~ index %} + + {{ item.encoder.class }} ({{ '%.2f'|format(item.encoder.time * 1000) }} ms) + + {% if item.encoding|length > 1 %} +
+ Show nested encoders +
+
    + {% for encoder in item.encoding %} +
  • x{{ encoder.calls }} {{ encoder.class }} ({{ '%.2f'|format(encoder.time * 1000) }} ms)
  • + {% endfor %} +
+
+
+ {% endif %} +{% endmacro %} + +{% macro render_time_cell(item) %} + {{ '%.2f'|format(item.time * 1000) }} ms +{% endmacro %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig index 0ed3ddc09b512..dbc32962310b2 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig @@ -9,7 +9,7 @@ {% set status_color = has_time_events and collector.duration > 1000 ? 'yellow' %} {% set icon %} - {{ include('@WebProfiler/Icon/time.svg') }} + {{ source('@WebProfiler/Icon/time.svg') }} {{ total_time }} ms {% endset %} @@ -30,7 +30,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/time.svg') }} + {{ source('@WebProfiler/Icon/time.svg') }} Performance {% endblock %} @@ -144,10 +144,10 @@ {% endblock %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig index a8a5c273656b5..e468ce2e82fe3 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig @@ -5,7 +5,7 @@ {% block toolbar %} {% if collector.messages|length %} {% set icon %} - {{ include('@WebProfiler/Icon/translation.svg') }} + {{ source('@WebProfiler/Icon/translation.svg') }} {% set status_color = collector.countMissings ? 'red' : collector.countFallbacks ? 'yellow' %} {% set error_count = collector.countMissings + collector.countFallbacks %} {{ error_count ?: collector.countDefines }} @@ -44,7 +44,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/translation.svg') }} + {{ source('@WebProfiler/Icon/translation.svg') }} Translation {% if collector.countMissings or collector.countFallbacks %} {% set error_count = collector.countMissings + collector.countFallbacks %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig index be84c19b1717c..0210ca012fed3 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/twig.html.twig @@ -3,7 +3,7 @@ {% block toolbar %} {% set time = collector.templatecount ? '%0.0f'|format(collector.time) : 'n/a' %} {% set icon %} - {{ include('@WebProfiler/Icon/twig.svg') }} + {{ source('@WebProfiler/Icon/twig.svg') }} {{ time }} ms {% endset %} @@ -32,7 +32,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/twig.svg') }} + {{ source('@WebProfiler/Icon/twig.svg') }} Twig {% endblock %} @@ -88,7 +88,7 @@ {%- set file = collector.templatePaths[template]|default(false) -%} {%- set link = file ? file|file_link(1) : false -%} - {{ include('@WebProfiler/Icon/twig.svg') }} + {{ source('@WebProfiler/Icon/twig.svg') }} {% if link %} {{ template }}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig index f3b7b7656e87c..0bb3ecf37ef6c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/validator.html.twig @@ -4,7 +4,7 @@ {% if collector.violationsCount > 0 or collector.calls|length %} {% set status_color = collector.violationsCount ? 'red' %} {% set icon %} - {{ include('@WebProfiler/Icon/validator.svg') }} + {{ source('@WebProfiler/Icon/validator.svg') }} {{ collector.violationsCount ?: collector.calls|length }} @@ -27,7 +27,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/validator.svg') }} + {{ source('@WebProfiler/Icon/validator.svg') }} Validator {% if collector.violationsCount > 0 %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index d811f6e3a2dab..3eb59b87b6467 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -39,11 +39,29 @@ if (typeof Sfjs === 'undefined' || typeof Sfjs.loadToolbar === 'undefined') { } if (navigator.clipboard) { - document.querySelectorAll('[data-clipboard-text]').forEach(function(element) { - removeClass(element, 'hidden'); - element.addEventListener('click', function() { - navigator.clipboard.writeText(element.getAttribute('data-clipboard-text')); - }) + document.addEventListener('readystatechange', () => { + if (document.readyState !== 'complete') { + return; + } + + document.querySelectorAll('[data-clipboard-text]').forEach(function (element) { + removeClass(element, 'hidden'); + element.addEventListener('click', function () { + navigator.clipboard.writeText(element.getAttribute('data-clipboard-text')); + + if (element.classList.contains("label")) { + let oldContent = element.textContent; + + element.textContent = "✅ Copied!"; + element.classList.add("status-success"); + + setTimeout(() => { + element.textContent = oldContent; + element.classList.remove("status-success"); + }, 7000); + } + }); + }); }); } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/cancel.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/cancel.html.twig index 6f1763d3a2937..2827f260f8f09 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/cancel.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/cancel.html.twig @@ -1,6 +1,6 @@ {% block toolbar %} {% set icon %} - {{ include('@WebProfiler/Icon/symfony.svg') }} + {{ source('@WebProfiler/Icon/symfony.svg') }} Loading… diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig index d04cf37e6c7b1..a2c826ab6efb5 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig @@ -1,6 +1,6 @@