diff --git a/.appveyor.yml b/.appveyor.yml index 8a880a9b3c063..67dcba9b569f6 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,10 +3,6 @@ clone_depth: 2 clone_folder: c:\projects\symfony image: Visual Studio 2019 -cache: - - composer.phar - - .phpunit -> phpunit - init: - SET PATH=c:\php;%PATH% - SET COMPOSER_NO_INTERACTION=1 @@ -17,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 @@ -46,11 +42,11 @@ install: - echo extension=php_fileinfo.dll >> php.ini-max - echo extension=php_pdo_sqlite.dll >> php.ini-max - echo extension=php_curl.dll >> php.ini-max + - echo extension=php_sodium.dll >> php.ini-max - copy /Y php.ini-max php.ini - cd c:\projects\symfony - - IF NOT EXIST composer.phar (appveyor DownloadFile https://github.com/composer/composer/releases/download/2.0.0/composer.phar) - - php composer.phar self-update --2 - - copy /Y .github\composer-config.json %APPDATA%\Composer\config.json + - appveyor DownloadFile https://getcomposer.org/download/latest-stable/composer.phar + - mkdir %APPDATA%\Composer && copy /Y .github\composer-config.json %APPDATA%\Composer\config.json - git config --global user.email "" - git config --global user.name "Symfony" - FOR /F "tokens=* USEBACKQ" %%F IN (`bash -c "grep ' VERSION = ' src/Symfony/Component/HttpKernel/Kernel.php | grep -o '[0-9][0-9]*\.[0-9]'"`) DO (SET SYMFONY_VERSION=%%F) diff --git a/.github/expected-missing-return-types.diff b/.github/expected-missing-return-types.diff index 4fd8581aba1ac..9c69e3869d510 100644 --- a/.github/expected-missing-return-types.diff +++ b/.github/expected-missing-return-types.diff @@ -1,22 +1,11 @@ # 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 +(echo "$head" && echo && git diff -U2 src/) > .github/expected-missing-return-types.diff git checkout composer.json src/ -diff --git a/composer.json b/composer.json -index c7b7eac4c7..2b60fb6a19 100644 ---- a/composer.json -+++ b/composer.json -@@ -180,5 +180,5 @@ - ], - "exclude-from-classmap": [ -- "**/Tests/" -+ - ] - }, 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 @@ -44,7 +33,7 @@ index 697e34cb77..9a1e4c5618 100644 */ - abstract protected function doRequest(object $request); + abstract protected function doRequest(object $request): object; - + /** @@ -460,5 +460,5 @@ abstract class AbstractBrowser * @return object @@ -60,6 +49,17 @@ index 697e34cb77..9a1e4c5618 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 @@ -122,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 @@ -147,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 @@ -156,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 0ed5649b8a..6c814bf82c 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 @@ -209,23 +209,47 @@ index 0ed5649b8a..6c814bf82c 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 0bd3426c07..b38caf989e 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 @@ -245,21 +269,21 @@ index 3af991a76f..742e2508f3 100644 */ - public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false); + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed; - + /** @@ -87,5 +87,5 @@ interface InputInterface * @throws InvalidArgumentException When argument given doesn't exist */ - public function getArgument(string $name); + public function getArgument(string $name): mixed; - + /** @@ -115,5 +115,5 @@ interface InputInterface * @throws InvalidArgumentException When option given doesn't exist */ - public function 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 c2824f4578..032f5c2318 100644 @@ -273,7 +297,7 @@ index c2824f4578..032f5c2318 100644 { if (\is_array($value)) { diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php -index b9dd838898..3a8cb63eac 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 @@ -284,7 +308,7 @@ index b9dd838898..3a8cb63eac 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 @@ -292,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 @@ -338,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 @@ -410,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 @@ -439,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 @@ -466,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 @@ -497,7 +521,7 @@ index 2b9066a511..1c9e9f5a26 100644 */ - public function getBlockPrefix(); + public function getBlockPrefix(): string; - + /** @@ -84,4 +84,4 @@ interface FormTypeInterface * @return string|null @@ -584,14 +608,14 @@ 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 @@ -611,6 +635,17 @@ index 125b6eae50..ac327e8981 100644 + 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 205c15b4cd..e93e460ed1 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -659,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 @@ -696,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() @@ -715,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 @@ -784,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 @@ -816,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 @@ -865,7 +900,7 @@ index f38069e471..0966eb3e89 100644 */ - public function decode(string $data, string $format, array $context = []); + public function decode(string $data, string $format, array $context = []): mixed; - + /** @@ -45,4 +45,4 @@ interface DecoderInterface * @return bool @@ -899,7 +934,7 @@ 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 a8943113c4..2983dabd0f 100644 +index 1abdb634ce..d2e7f41562 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -138,5 +138,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer @@ -916,35 +951,35 @@ index a8943113c4..2983dabd0f 100644 + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if (!isset($context['cache_key'])) { -@@ -279,5 +279,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)) { -@@ -341,5 +341,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; - + /** -@@ -348,5 +348,5 @@ 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; - + /** -@@ -355,5 +355,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer +@@ -356,5 +356,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */) + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool { return class_exists($type) || (interface_exists($type, false) && $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); -@@ -363,5 +363,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 = []) @@ -960,7 +995,7 @@ index 1c708738a1..3b6c9d5056 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; - + /** @@ -57,4 +57,4 @@ interface DenormalizerInterface * @return bool @@ -977,7 +1012,7 @@ index 741f19e50b..acf3be931b 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; - + /** @@ -48,4 +48,4 @@ interface NormalizerInterface * @return bool @@ -994,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 @@ -1005,7 +1040,7 @@ index 4c088b94f9..86107a636d 100644 */ - abstract protected function canBeExtracted(string $file); + abstract protected function canBeExtracted(string $file): bool; - + /** * @return iterable */ diff --git a/.github/psalm/.gitignore b/.github/psalm/.gitignore index d6b7ef32c8478..53021ab087be4 100644 --- a/.github/psalm/.gitignore +++ b/.github/psalm/.gitignore @@ -1,2 +1,4 @@ * !.gitignore +!stubs +!stubs/* diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index c9d097ec9a4aa..bf62788b7a81e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -8,6 +8,10 @@ defaults: run: shell: bash +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: tests: @@ -16,7 +20,7 @@ jobs: strategy: matrix: - php: ['8.0'] + php: ['8.1'] services: postgres: @@ -125,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 @@ -149,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::" @@ -172,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 4443fe5b83f9b..065c1a081cc1f 100644 --- a/.github/workflows/intl-data-tests.yml +++ b/.github/workflows/intl-data-tests.yml @@ -12,6 +12,10 @@ defaults: run: shell: bash +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: tests: name: Tests @@ -42,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/phpunit-bridge.yml b/.github/workflows/phpunit-bridge.yml index 5bbcc63041020..c135d13e3df4a 100644 --- a/.github/workflows/phpunit-bridge.yml +++ b/.github/workflows/phpunit-bridge.yml @@ -12,6 +12,10 @@ defaults: run: shell: bash +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: lint: name: Lint diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 3eeb7201c486d..0b7e5b18c658e 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -7,6 +7,10 @@ defaults: run: shell: bash +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: psalm: name: Psalm @@ -16,7 +20,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.0' + php-version: '8.1' extensions: "json,couchbase,memcached,mongodb,redis,xsl,ldap,dom" ini-values: "memory_limit=-1" coverage: none @@ -35,18 +39,13 @@ jobs: ([ -d "$COMPOSER_HOME" ] || mkdir "$COMPOSER_HOME") && cp .github/composer-config.json "$COMPOSER_HOME/config.json" export COMPOSER_ROOT_VERSION=$(grep ' VERSION = ' src/Symfony/Component/HttpKernel/Kernel.php | grep -P -o '[0-9]+\.[0-9]+').x-dev composer remove --dev --no-update --no-interaction symfony/phpunit-bridge - composer require --no-update psalm/phar phpunit/phpunit:^9.5 php-http/discovery psr/event-dispatcher mongodb/mongodb - - echo "::group::composer update" - composer update --no-progress --ansi - git checkout composer.json - echo "::endgroup::" - - ./vendor/bin/psalm.phar --version + composer require --no-progress --ansi psalm/phar phpunit/phpunit:^9.5 php-http/discovery psr/event-dispatcher mongodb/mongodb - name: Generate Psalm baseline run: | + git checkout composer.json git checkout -m ${{ github.base_ref }} + ./vendor/bin/psalm.phar --set-baseline=.github/psalm/psalm.baseline.xml --no-progress git checkout -m FETCH_HEAD diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 7c1e9139b9b9d..c3ad676bab3a4 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -8,6 +8,10 @@ defaults: run: shell: bash +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: tests: @@ -20,12 +24,12 @@ jobs: matrix: include: - php: '8.1' - - php: '8.0' + - php: '8.1' mode: high-deps - php: '8.1' mode: low-deps - php: '8.2' - mode: experimental + #mode: experimental fail-fast: false runs-on: ubuntu-20.04 @@ -61,7 +65,7 @@ jobs: ([ -d "$COMPOSER_HOME" ] || mkdir "$COMPOSER_HOME") && cp .github/composer-config.json "$COMPOSER_HOME/config.json" echo COLUMNS=120 >> $GITHUB_ENV - echo PHPUNIT="$(pwd)/phpunit --exclude-group tty,benchmark,intl-data" >> $GITHUB_ENV + echo PHPUNIT="$(pwd)/phpunit --exclude-group tty,benchmark,intl-data,integration" >> $GITHUB_ENV echo COMPOSER_UP='composer update --no-progress --ansi' >> $GITHUB_ENV SYMFONY_VERSIONS=$(git ls-remote -q --heads | cut -f2 | grep -o '/[1-9][0-9]*\.[0-9].*' | sort -V) @@ -140,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 @@ -218,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/CHANGELOG-6.0.md b/CHANGELOG-6.0.md index 20675c834b94c..f92727fee106c 100644 --- a/CHANGELOG-6.0.md +++ b/CHANGELOG-6.0.md @@ -7,6 +7,74 @@ in 6.0 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v6.0.0...v6.0.1 +* 6.0.6 (2022-03-05) + + * bug #45619 [redis-messenger] remove undefined array key warnings (PhilETaylor) + * bug #45637 [Cache] do not pass DBAL connections to PDO adapters (xabbuh) + * bug #45631 [HttpFoundation] Fix PHP 8.1 deprecation in `Response::isNotModified` (HypeMC) + * bug #45610 [HttpKernel] Guard against bad profile data (nicolas-grekas) + * bug #45532 Fix deprecations on PHP 8.2 (nicolas-grekas) + * bug #45595 [FrameworkBundle] Fix resetting container between tests (nicolas-grekas) + * bug #45590 [Console] Revert StringInput bc break from #45088 (bobthecow) + * bug #45585 [HttpClient] fix checking for unset property on PHP <= 7.1.4 (nicolas-grekas) + * bug #45583 [WebProfilerBundle] Fixes HTML syntax regression introduced by #44570 (xavismeh) + +* 6.0.5 (2022-02-28) + + * bug #45351 [WebProfilerBundle] Log section minor fixes (missing "notice" filter, log priority, accessibility) (Amunak) + * bug #44967 [Validator] Multi decimal to alpha for CssColor validator (tilimac) + * bug #45546 [Console] Fix null handling in formatAndWrap() (derrabus) + * bug #44570 [WebProfilerBundle] add nonces to profiler (garak) + * bug #44839 MailerInterface: failed exception contract when enabling messenger (Giorgio Premi) + * bug #45526 [Lock] Release Locks from Internal Store on Postgres waitAndSave* (chrisguitarguy) + * bug #45529 [DependencyInjection] Don't reset env placeholders during compilation (nicolas-grekas) + * bug #45527 [HttpClient] Fix overriding default options with null (nicolas-grekas) + * bug #45531 [Serializer] Fix passing null to str_contains() (Erwin Dirks) + * bug #42458 [Validator][Tests] Fix AssertingContextualValidator not throwing on remaining expectations (fancyweb) + * bug #45279 [Messenger] Fix dealing with unexpected payload in Redis transport (nicoalonso) + * bug #45496 [VarDumper] Fix dumping mysqli_driver instances (nicolas-grekas) + * bug #45495 [HttpFoundation] Fix missing ReturnTypeWillChange attributes (luxemate) + * bug #45482 [Cache] Add missing log when saving namespace (developer-av) + * bug #45479 [HttpKernel] Reset services between requests performed by KernelBrowser (nicolas-grekas) + * bug #44650 [Serializer] Make document type nodes ignorable (boenner) + * bug #45469 [SecurityBundle] fix autoconfiguring Monolog's ProcessorInterface (nicolas-grekas) + * bug #45414 [FrameworkBundle] KernelTestCase resets internal state on tearDown (core23) + * bug #45430 [Dotenv] Fix reading config for symfony/runtime when running dump command (nicolas-grekas) + * bug #45460 [Intl] fix wrong offset timezone PHP 8.1 (Lenny4) + * bug #45462 [HttpKernel] Fix extracting controller name from closures (nicolas-grekas) + * bug #45463 [Security/Http] Fix getting password-upgrader when user-loader is a closure (nicolas-grekas) + * bug #45424 [DependencyInjection] Fix type binding (sveneld) + * bug #45426 [Runtime] Fix dotenv_overload with commands (fancyweb) + * bug #44259 [Security] AccountStatusException::$user should be nullable (Cantepie) + * bug #45391 [Serializer] Ensuring end of line character apply with constructor settings in CSV encoder (bizley) + * bug #45323 [Serializer] Fix ignored callbacks in denormalization (benjaminmal) + * bug #45399 [FrameworkBundle] Fix sorting bug in sorting of tagged services by priority (Ahummeling) + * bug #45338 [Mailer] Fix string-cast of exceptions thrown by authenticator in EsmtpTransport (wikando-ck) + * bug #45339 [Cache] fix error handling when using Redis (nicolas-grekas) + * bug #45331 [Security]  Fix wrong authenticator class in debug logs (chalasr) + * bug #45322 Fix generic type for FormErrorIterator (akalineskou) + * bug #45281 [Cache] Fix connecting to Redis via a socket file (alebedev80) + * bug #45289 [FrameworkBundle] Fix log channel of TagAwareAdapter (fancyweb) + * bug #45306 [PropertyAccessor] Add missing TypeError catch (b1rdex) + * bug #44868 [DependencyInjection][FrameworkBundle] Fix using PHP 8.1 enum as parameters (ogizanagi) + * bug #45298 [HttpKernel] Fix FileLinkFormatter with empty xdebug.file_link_format (fancyweb) + * bug #45299 [DependencyInjection] Fix AsEventListener not working on decorators (LANGERGabrielle) + * bug #45302 [HttpKernel][WebProfilerBundle] Fixed error count by log not displayed in WebProfilerBundle (SVillette) + * bug #45219 [WebProfilerBundle] Fixes weird spacing in log message context/trace output (jennevdmeer) + * bug #45290 [Notifier] fix Microsoft Teams webhook url (christophkoenig) + * bug #45274 [Mailer] allow Mailchimp to handle multiple TagHeader's (kbond) + * bug #45275 [Mailer] ensure only a single tag can be used with Postmark (kbond) + * bug #45261 [HttpClient] Fix Content-Length header when possible (nicolas-grekas) + * bug #45263 [Routing] AnnotationDirectoryLoader::load() may return null (mhujer) + * bug #45258 [DependencyInjection] Don't dump polyfilled classes in preload script (nicolas-grekas) + * bug #38534 [Serializer] make XmlEncoder stateless thus reentrant (connorhu) + * bug #42253 [Form] Do not fix URL protocol for relative URLs (bogkonstantin) + * bug #45256 [DomCrawler] ignore bad charsets (nicolas-grekas) + * bug #45255 [PropertyAccess] Fix handling of uninitialized property of parent class (filiplikavcan) + * bug #45204 [Validator] Fix minRatio and maxRatio when getting rounded (alexander-schranz) + * bug #45240 [Console] Revert StringInput bc break from #45088 (bobthecow) + * bug #45243 [DoctrineBridge] Fix compatibility with doctrine/orm 3 in Id generators (ostrolucky) + * 6.0.4 (2022-01-29) * security #cve-2022-xxxx [FrameworkBundle] Enable CSRF in FORM by default (jderusse) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d287e27ee871e..e2781d32f4a30 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -12,13 +12,13 @@ The Symfony Connect username in parenthesis allows to get more information - Tobias Schultze (tobion) - Robin Chalas (chalas_r) - Christophe Coevoet (stof) - - Wouter De Jong (wouterj) - Jérémy DERUSSÉ (jderusse) + - Wouter De Jong (wouterj) - Grégoire Pineau (lyrixx) - Maxime Steinhausser (ogizanagi) - Kévin Dunglas (dunglas) - - Jordi Boggiano (seldaek) - Thomas Calvet (fancyweb) + - Jordi Boggiano (seldaek) - Victor Berchet (victor) - Javier Eguiluz (javier.eguiluz) - Ryan Weaver (weaverryan) @@ -76,10 +76,10 @@ The Symfony Connect username in parenthesis allows to get more information - Saša Stamenković (umpirsky) - Peter Rehm (rpet) - Henrik Bjørnskov (henrikbjorn) - - Miha Vrhovnik - Antoine M (amakdessi) - - Diego Saint Esteben (dii3g0) + - Miha Vrhovnik - Mathieu Piot (mpiot) + - Diego Saint Esteben (dii3g0) - Konstantin Kudryashov (everzet) - Vladimir Reznichenko (kalessil) - Bilal Amarni (bamarni) @@ -93,37 +93,37 @@ The Symfony Connect username in parenthesis allows to get more information - Eric Clemmons (ericclemmons) - Graham Campbell (graham) - Charles Sarrazin (csarrazi) + - Alexander Schranz (alexander-schranz) - Vasilij Dusko - Douglas Greenshields (shieldo) - David Buchmann (dbu) - - Alexander Schranz (alexander-schranz) - Arnout Boks (aboks) - Deni - Henrik Westphal (snc) - Dariusz Górecki (canni) - Fran Moreno (franmomu) + - Jérôme Vasseur (jvasseur) - Mathieu Santostefano (welcomattic) - Dariusz Ruminski - - Jérôme Vasseur (jvasseur) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) - 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) - - Alexandre Daubois (alexandre-daubois) - Julien Falque (julienfalque) - Baptiste Clavié (talus) + - Massimiliano Arione (garak) + - Mathias Arlaud (mtarld) - Antoine Hérault (herzult) - Paráda József (paradajozsef) - Vincent Langlet (deviling) - - Massimiliano Arione (garak) - - Mathias Arlaud (mtarld) - Arnaud Le Blanc (arnaud-lb) - Przemysław Bogusz (przemyslaw-bogusz) - Maxime STEINHAUSSER @@ -182,6 +182,7 @@ The Symfony Connect username in parenthesis allows to get more information - Juti Noppornpitak (shiroyuki) - Simon Berger - Anthony MARTIN (xurudragon) + - Alexander Menshchikov (zmey_kk) - Sebastian Hörl (blogsh) - Daniel Gomes (danielcsgomes) - Hidenori Goto (hidenorigoto) @@ -192,7 +193,6 @@ The Symfony Connect username in parenthesis allows to get more information - Guilherme Blanco (guilhermeblanco) - Marco Pivetta (ocramius) - SpacePossum - - Alexander Menshchikov (zmey_kk) - Pablo Godel (pgodel) - Andreas Braun - Jérémie Augustin (jaugustin) @@ -236,6 +236,7 @@ The Symfony Connect username in parenthesis allows to get more information - Matthieu Ouellette-Vachon (maoueh) - Michał Pipa (michal.pipa) - Dawid Nowak + - Martin Hujer (martinhujer) - Roman Martinuk (a2a4) - Amal Raghav (kertz) - Jonathan Ingram (jonathaningram) @@ -264,7 +265,6 @@ The Symfony Connect username in parenthesis allows to get more information - Dorian Villet (gnutix) - Michaël Perrin (michael.perrin) - Sergey Linnik (linniksa) - - Martin Hujer (martinhujer) - Richard Miller (mr_r_miller) - Mario A. Alvarez Garcia (nomack84) - Dennis Benkert (denderello) @@ -281,6 +281,7 @@ The Symfony Connect username in parenthesis allows to get more information - Benjamin Dulau (dbenjamin) - Baptiste Lafontaine (magnetik) - Mathieu Lemoine (lemoinem) + - Justin Hileman (bobthecow) - Denis Brumann (dbrumann) - Christian Schmidt - Andreas Hucks (meandmymonkey) @@ -308,6 +309,7 @@ The Symfony Connect username in parenthesis allows to get more information - Dominique Bongiraud - dFayet - Jeremy Livingston (jeremylivingston) + - Karoly Gossler (connorhu) - soyuka - Michael Lee (zerustech) - Matthieu Auger (matthieuauger) @@ -318,7 +320,6 @@ The Symfony Connect username in parenthesis allows to get more information - jeff - John Kary (johnkary) - fd6130 (fdtvui) - - Justin Hileman (bobthecow) - Blanchon Vincent (blanchonvincent) - Maciej Malarz (malarzm) - Michele Orselli (orso) @@ -346,7 +347,6 @@ The Symfony Connect username in parenthesis allows to get more information - Mantis Development - Loïc Faugeron - quentin neyrat (qneyrat) - - Karoly Gossler (connorhu) - Marcin Szepczynski (czepol) - Rob Frawley 2nd (robfrawley) - Ahmed Raafat @@ -415,8 +415,10 @@ The Symfony Connect username in parenthesis allows to get more information - Wodor Wodorski - Guilhem N (guilhemn) - Mohammad Emran Hasan (phpfour) + - Christopher Davis (chrisguitarguy) - Dmitriy Mamontov (mamontovdmitriy) - Ben Ramsey (ramsey) + - Hugo Alliaume (kocal) - Laurent Masforné (heisenberg) - Sergey (upyx) - Giorgio Premi @@ -482,6 +484,7 @@ The Symfony Connect username in parenthesis allows to get more information - Aurelijus Valeiša (aurelijus) - Jan Decavele (jandc) - Gustavo Piltcher + - flack (flack) - Stepan Tanasiychuk (stfalcon) - Ivan Kurnosov - Tiago Ribeiro (fixe) @@ -508,7 +511,6 @@ The Symfony Connect username in parenthesis allows to get more information - Mark Challoner (markchalloner) - Loïc Frémont (loic425) - Oleksandr Barabolia (oleksandrbarabolia) - - Christopher Davis (chrisguitarguy) - ivan - Greg Anderson - Tri Pham (phamuyentri) @@ -539,6 +541,7 @@ The Symfony Connect username in parenthesis allows to get more information - Dmytro Borysovskyi (dmytr0) - Tomasz Kowalczyk (thunderer) - Artur Eshenbrener + - Dries Vints - Thomas Perez (scullwm) - Yoann RENARD (yrenard) - Felix Labrecque @@ -548,6 +551,7 @@ The Symfony Connect username in parenthesis allows to get more information - Tim Goudriaan (codedmonkey) - Tarmo Leppänen (tarlepp) - Martin Auswöger + - Hubert Lenoir (hubert_lenoir) - Robbert Klarenbeek (robbertkl) - Hamza Makraz (makraz) - Eric Masoero (eric-masoero) @@ -556,7 +560,6 @@ The Symfony Connect username in parenthesis allows to get more information - hossein zolfi (ocean) - Clément Gautier (clementgautier) - Koen Reiniers (koenre) - - Hugo Alliaume (kocal) - Sanpi - Eduardo Gulias (egulias) - giulio de donato (liuggio) @@ -620,7 +623,6 @@ The Symfony Connect username in parenthesis allows to get more information - hubert lecorche (hlecorche) - Vladyslav Loboda - fritzmg - - flack (flack) - Marc Morales Valldepérez (kuert) - Jean-Baptiste GOMOND (mjbgo) - Vadim Kharitonov (virtuozzz) @@ -716,7 +718,6 @@ The Symfony Connect username in parenthesis allows to get more information - Jerzy (jlekowski) - Raulnet - Christian Wahler - - Dries Vints - Giso Stallenberg (gisostallenberg) - Gintautas Miselis - Rob Bast @@ -724,6 +725,7 @@ The Symfony Connect username in parenthesis allows to get more information - Pierre Rineau - Andreas Leathley (iquito) - Soufian EZ-ZANTAR (soezz) + - Arun Philip - Zander Baldwin - Marek Zajac - Adam Harvey @@ -769,6 +771,7 @@ The Symfony Connect username in parenthesis allows to get more information - nikos.sotiropoulos - Eduardo Oliveira (entering) - Oleksii Zhurbytskyi + - Bilge - Ilya Antipenko (aivus) - Ricardo Oliveira (ricardolotr) - Roy Van Ginneken (rvanginneken) @@ -799,7 +802,6 @@ The Symfony Connect username in parenthesis allows to get more information - alexpods - Dennis Langen (nijusan) - Adrien Wilmet (adrienfr) - - Hubert Lenoir (hubert_lenoir) - Adam Szaraniec (mimol) - Dariusz Ruminski - Erik Trapman (eriktrapman) @@ -926,6 +928,7 @@ The Symfony Connect username in parenthesis allows to get more information - Nicolas Martin (cocorambo) - Jon Gotlin (jongotlin) - Adrian Nguyen (vuphuong87) + - benjaminmal - Khoo Yong Jun - Sebastian Blum - Laurent Clouet @@ -973,6 +976,7 @@ The Symfony Connect username in parenthesis allows to get more information - Geoffrey Brier (geoffrey-brier) - Alexandre Parent - Roger Guasch (rogerguasch) + - DT Inier (gam6itko) - Vladimir Tsykun - Andrei O - Dustin Dobervich (dustin10) @@ -1050,7 +1054,6 @@ The Symfony Connect username in parenthesis allows to get more information - maxime.perrimond - Sascha Grossenbacher - cthulhu - - Arun Philip - Rémi Leclerc - Jonas Hünig - Szijarto Tamas @@ -1061,6 +1064,7 @@ The Symfony Connect username in parenthesis allows to get more information - Kristijan Kanalas - Stephan Vock - Benjamin Zikarsky (bzikarsky) + - “Filip - Marion Hurteau - Dmitrii Lozhkin - Sobhan Sharifi (50bhan) @@ -1176,6 +1180,7 @@ The Symfony Connect username in parenthesis allows to get more information - Wickex - tuqqu - Neagu Cristian-Doru (cristian-neagu) + - Dude (b1rdex) - Dave Marshall (davedevelopment) - Jakub Kulhan (jakubkulhan) - Shaharia Azam @@ -1203,6 +1208,7 @@ The Symfony Connect username in parenthesis allows to get more information - SnakePin - vladimir.panivko - Josiah (josiah) + - Dennis Væversted (srnzitcom) - Guillaume Verstraete (versgui) - Joschi Kuphal - John Bohn (jbohn) @@ -1221,7 +1227,6 @@ The Symfony Connect username in parenthesis allows to get more information - Christian Soronellas (theunic) - kick-the-bucket - fedor.f - - Bilge - Yosmany Garcia (yosmanyga) - Jeremiasz Major - Wouter de Wild @@ -1265,6 +1270,7 @@ The Symfony Connect username in parenthesis allows to get more information - Zhuravlev Alexander (scif) - Stefano Degenkamp (steef) - James Michael DuPont + - Christian Gripp (core23) - Jake (jakesoft) - Flinsch - Quentin Dreyer @@ -1625,6 +1631,7 @@ The Symfony Connect username in parenthesis allows to get more information - Georgi Georgiev - Maximilian Berghoff (electricmaxxx) - nacho + - TristanPouliquen - Piotr Antosik (antek88) - mwos - Volker Killesreiter (ol0lll) @@ -1643,6 +1650,7 @@ The Symfony Connect username in parenthesis allows to get more information - Ken Marfilla (marfillaster) - benatespina (benatespina) - Denis Kop + - Andrey Lebedev (alebedev) - Jean-Guilhem Rouel (jean-gui) - Yoann MOROCUTTI - jfcixmedia @@ -1690,7 +1698,6 @@ The Symfony Connect username in parenthesis allows to get more information - Jakub Sacha - Julius Kiekbusch - Olaf Klischat - - benjaminmal - orlovv - Claude Dioudonnat - Jonathan Hedstrom @@ -1734,6 +1741,7 @@ The Symfony Connect username in parenthesis allows to get more information - Péter Buri (burci) - Evgeny Efimov (edefimov) - kaiwa + - Daniel Badura - Charles Sanquer (csanquer) - Albert Ganiev (helios-ag) - Neil Katin @@ -1762,6 +1770,7 @@ The Symfony Connect username in parenthesis allows to get more information - Amine Yakoubi - Eduardo García Sanz (coma) - Sergio (deverad) + - Arend Hummeling - Makdessi Alex - fduch (fduch) - Juan Miguel Besada Vidal (soutlink) @@ -1829,13 +1838,13 @@ The Symfony Connect username in parenthesis allows to get more information - Vlad Gapanovich (gapik) - Alexander Kurilo (kamazee) - Nyro (nyro) + - Konstantin Bogomolov - Marco - Marc Torres - Mark Spink - cesar - Alberto Aldegheri - Cesar Scur (cesarscur) - - “Filip - Dmitri Petmanson - heccjj - Alexandre Melard @@ -1918,6 +1927,7 @@ The Symfony Connect username in parenthesis allows to get more information - abluchet - Ruud Arentsen - Harald Tollefsen + - Tobias Bönner - Matthieu - Arend-Jan Tetteroo - Albin Kerouaton @@ -1979,6 +1989,7 @@ The Symfony Connect username in parenthesis allows to get more information - Volodymyr Kupriienko (greeflas) - Serhiy Lunak (slunak) - Wojciech Błoszyk (wbloszyk) + - Jiri Barous - Giorgio Premi - abunch - tamcy @@ -2000,6 +2011,7 @@ The Symfony Connect username in parenthesis allows to get more information - Frédéric Bouchery (fbouchery) - Patrick Daley (padrig) - Foxprodev + - developer-av - Max Summe - WedgeSama - Dale.Nash @@ -2103,6 +2115,7 @@ The Symfony Connect username in parenthesis allows to get more information - Simon Neidhold - Valentin VALCIU - Jeremiah VALERIE + - Alexandre Beaujour - Julien Menth - George Yiannoulopoulos - Yannick Snobbert @@ -2116,6 +2129,7 @@ The Symfony Connect username in parenthesis allows to get more information - bill moll - Benjamin Bender - PaoRuby + - Bizley - Jared Farrish - karl.rixon - raplider @@ -2236,12 +2250,14 @@ The Symfony Connect username in parenthesis allows to get more information - riadh26 - Konstantinos Alexiou - Andrii Boiko + - Dilek Erkut - Harold Iedema - WaiSkats - Morimoto Ryosuke - Ikhsan Agustian - Arnau González (arnaugm) - Simon Bouland (bouland) + - Christoph König (chriskoenig) - Jibé Barth (jibbarth) - Jm Aribau (jmaribau) - Matthew Foster (mfoster) @@ -2266,6 +2282,7 @@ The Symfony Connect username in parenthesis allows to get more information - Phil Davis - Gleb Sidora - David Stone + - Giorgio Premi - Gerhard Seidel (gseidel) - Jovan Perovic (jperovic) - Pablo Maria Martelletti (pmartelletti) @@ -2295,7 +2312,6 @@ The Symfony Connect username in parenthesis allows to get more information - Mickael GOETZ - Maciej Schmidt - botbotbot - - Dennis Væversted - Timon van der Vorm - nuncanada - František Bereň @@ -2390,6 +2406,7 @@ The Symfony Connect username in parenthesis allows to get more information - Roy-Orbison - Aaron Somi - kshida + - Yasmany Cubela Medina (bitgandtter) - Michał Dąbrowski (defrag) - Aryel Tupinamba (dfkimera) - Hans Höchtl (hhoechtl) @@ -2400,6 +2417,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jawira Portugal (jawira) - Johannes Müller (johmue) - Jordi Llonch (jordillonch) + - Roman Igoshin (masterro) - Nicholas Ruunu (nicholasruunu) - Jeroen van den Nieuwenhuisen (nieuwenhuisen) - Cyril Pascal (paxal) @@ -2454,6 +2472,7 @@ The Symfony Connect username in parenthesis allows to get more information - Adam - Ivo - Ismo Vuorinen + - Valentin - Sören Bernstein - devel - taiiiraaa @@ -2556,6 +2575,7 @@ The Symfony Connect username in parenthesis allows to get more information - Veres Lajos - Ernest Hymel - Andrea Civita + - Nicolás Alonso - LoginovIlya - Nick Chiu - grifx @@ -2596,10 +2616,12 @@ The Symfony Connect username in parenthesis allows to get more information - David Windell - Frank Jogeleit - Ondřej Frei + - Volodymyr Panivko - Gabriel Birke - skafandri - Derek Bonner - martijn + - Jenne van der Meer - Storkeus - Alan Chen - Anton Zagorskii @@ -2610,10 +2632,12 @@ The Symfony Connect username in parenthesis allows to get more information - Even André Fiskvik - Agata - dakur + - Matthias Schmidt - florian-michael-mast - Александр Ли - Arjan Keeman - Vlad Dumitrache + - Alex Kalineskou - Erik van Wingerden - Valouleloup - robmro27 @@ -2675,6 +2699,7 @@ The Symfony Connect username in parenthesis allows to get more information - Kevin Verschaeve (keversc) - Kevin Herrera (kherge) - Luis Ramón López López (lrlopez) + - Vladislav Nikolayev (luxemate) - Martin Mandl (m2mtech) - Mehdi Mabrouk (mehdidev) - Bart Reunes (metalarend) @@ -2693,10 +2718,12 @@ The Symfony Connect username in parenthesis allows to get more information - Pablo Monterde Perez (plebs) - Pierre-Olivier Vares (povares) - Jimmy Leger (redpanda) + - Samaël Villette (samadu61) - Marcin Szepczynski (szepczynski) - Cyrille Jouineau (tuxosaurus) - Vladimir Chernyshev (volch) - Wim Godden (wimg) + - Xav` (xavismeh) - Yorkie Chadwick (yorkie76) - Maxime Aknin (3m1x4m) - Geordie @@ -2721,6 +2748,7 @@ The Symfony Connect username in parenthesis allows to get more information - Gabriel Moreira - Alexey Popkov - ChS + - michal - Alexis MARQUIS - Joseph Deray - Damian Sromek @@ -2792,6 +2820,7 @@ The Symfony Connect username in parenthesis allows to get more information - Michel Bardelmeijer - Tomas Kmieliauskas - Ikko Ashimine + - Erwin Dirks - Brad Jones - Billie Thompson - lol768 @@ -2801,6 +2830,7 @@ The Symfony Connect username in parenthesis allows to get more information - Johannes - Jörg Rühl - George Dietrich + - jannick-holm - wesleyh - sergey - Menno Holtkamp @@ -2811,6 +2841,7 @@ The Symfony Connect username in parenthesis allows to get more information - Michael Genereux - patrick-mcdougle - Dariusz Czech + - Clemens Krack - Bruno Baguette - Jack Wright - MrNicodemuz @@ -2867,6 +2898,8 @@ The Symfony Connect username in parenthesis allows to get more information - bokonet - Arrilot - ampaze + - Gabrielle Langer + - Chris McGehee - Markus Staab - Pierre-Louis LAUNAY - djama @@ -2914,6 +2947,7 @@ The Symfony Connect username in parenthesis allows to get more information - Ilya Bulakh - David Soria Parra - Sergiy Sokolenko + - Cantepie - detinkin - Ahmed Abdulrahman - dinitrol @@ -2971,7 +3005,6 @@ The Symfony Connect username in parenthesis allows to get more information - Juan Ases García (ases) - Siragusa (asiragusa) - Daniel Basten (axhm3a) - - Dude (b1rdex) - Benedict Massolle (bemas) - Gerard Berengue Llobera (bere) - Bernd Matzner (bmatzner) @@ -2981,7 +3014,6 @@ The Symfony Connect username in parenthesis allows to get more information - Choong Wei Tjeng (choonge) - Kousuke Ebihara (co3k) - Loïc Vernet (coil) - - Christian Gripp (core23) - Christoph Schaefer (cvschaefer) - Damon Jones (damon__jones) - Alexandre Fiocre (demos77) @@ -3087,6 +3119,7 @@ The Symfony Connect username in parenthesis allows to get more information - Florent Cailhol - szymek - Ryan Linnit + - a.dmitryuk - Kovacs Nicolas - craigmarvelley - Stano Turza diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index e70e3e5d2a917..718b729e55a2c 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -146,6 +146,11 @@ Inflector * The component has been removed, use `EnglishInflector` from the String component instead. +Ldap +---- + + * Remove `LdapAuthenticator::createAuthenticatedToken()`, use `LdapAuthenticator::createToken()` instead + Lock ---- diff --git a/UPGRADE-6.1.md b/UPGRADE-6.1.md index 7d24e202f8410..1b66c0ece04dc 100644 --- a/UPGRADE-6.1.md +++ b/UPGRADE-6.1.md @@ -4,6 +4,7 @@ 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 @@ -11,6 +12,24 @@ 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 ---------- @@ -19,8 +38,13 @@ Serializer * 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 c1987e3d1dd1c..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": { @@ -131,7 +130,6 @@ "masterminds/html5": "^2.6", "monolog/monolog": "^1.25.1|^2", "nyholm/psr7": "^1.0", - "paragonie/sodium_compat": "^1.8", "pda/pheanstalk": "^4.0", "php-http/httplug": "^1.0|^2.0", "phpstan/phpdoc-parser": "^1.0", @@ -177,6 +175,9 @@ "files": [ "src/Symfony/Component/String/Resources/functions.php" ], + "classmap": [ + "src/Symfony/Component/Cache/Traits/ValueWrapper.php" + ], "exclude-from-classmap": [ "**/Tests/" ] diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5ad6175de7a31..5f5207576f4f6 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -74,13 +74,14 @@ Cache\IntegrationTests - Symfony\Component\Cache - Symfony\Component\Cache\Tests\Fixtures - Symfony\Component\Cache\Tests\Traits - Symfony\Component\Cache\Traits - Symfony\Component\Console - Symfony\Component\HttpFoundation - Symfony\Component\Uid + Symfony\Bridge\Doctrine\Middleware\Debug + Symfony\Component\Cache + Symfony\Component\Cache\Tests\Fixtures + Symfony\Component\Cache\Tests\Traits + Symfony\Component\Cache\Traits + Symfony\Component\Console + Symfony\Component\HttpFoundation + Symfony\Component\Uid diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index a8e55ee881e01..7acc2e8e96e95 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -15,6 +15,7 @@ use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; use Doctrine\Persistence\ManagerRegistry; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; @@ -31,17 +32,19 @@ class DoctrineDataCollector extends DataCollector private ManagerRegistry $registry; private array $connections; private array $managers; + private ?DebugDataHolder $debugDataHolder; /** * @var array */ private array $loggers = []; - public function __construct(ManagerRegistry $registry) + public function __construct(ManagerRegistry $registry, DebugDataHolder $debugDataHolder = null) { $this->registry = $registry; $this->connections = $registry->getConnectionNames(); $this->managers = $registry->getManagerNames(); + $this->debugDataHolder = $debugDataHolder; } /** @@ -56,23 +59,43 @@ public function addLogger(string $name, DebugStack $logger) * {@inheritdoc} */ public function collect(Request $request, Response $response, \Throwable $exception = null) + { + $this->data = [ + 'queries' => $this->collectQueries(), + 'connections' => $this->connections, + 'managers' => $this->managers, + ]; + } + + private function collectQueries(): array { $queries = []; + + if (null !== $this->debugDataHolder) { + foreach ($this->debugDataHolder->getData() as $name => $data) { + $queries[$name] = $this->sanitizeQueries($name, $data); + } + + return $queries; + } + foreach ($this->loggers as $name => $logger) { $queries[$name] = $this->sanitizeQueries($name, $logger->queries); } - $this->data = [ - 'queries' => $queries, - 'connections' => $this->connections, - 'managers' => $this->managers, - ]; + return $queries; } public function reset() { $this->data = []; + if (null !== $this->debugDataHolder) { + $this->debugDataHolder->reset(); + + return; + } + foreach ($this->loggers as $logger) { $logger->queries = []; $logger->currentQuery = 0; 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/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/Middleware/Debug/Connection.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php new file mode 100644 index 0000000000000..d085b0af0e3de --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; +use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; +use Doctrine\DBAL\Driver\Result; +use Doctrine\DBAL\Driver\Statement as DriverStatement; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Connection extends AbstractConnectionMiddleware +{ + private $nestingLevel = 0; + private $debugDataHolder; + private $stopwatch; + private $connectionName; + + public function __construct(ConnectionInterface $connection, DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch, string $connectionName) + { + parent::__construct($connection); + + $this->debugDataHolder = $debugDataHolder; + $this->stopwatch = $stopwatch; + $this->connectionName = $connectionName; + } + + public function prepare(string $sql): DriverStatement + { + return new Statement( + parent::prepare($sql), + $this->debugDataHolder, + $this->connectionName, + $sql + ); + } + + public function query(string $sql): Result + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + if (null !== $this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + + $query->start(); + + try { + $result = parent::query($sql); + } finally { + $query->stop(); + + if (null !== $this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + + return $result; + } + + public function exec(string $sql): int + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + if (null !== $this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + + $query->start(); + + try { + $affectedRows = parent::exec($sql); + } finally { + $query->stop(); + + if (null !== $this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + + return $affectedRows; + } + + public function beginTransaction(): bool + { + $query = null; + if (1 === ++$this->nestingLevel) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"START TRANSACTION"')); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + + if (null !== $query) { + $query->start(); + } + + try { + $ret = parent::beginTransaction(); + } finally { + if (null !== $query) { + $query->stop(); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + + return $ret; + } + + public function commit(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"COMMIT"')); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + + if (null !== $query) { + $query->start(); + } + + try { + $ret = parent::commit(); + } finally { + if (null !== $query) { + $query->stop(); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + + return $ret; + } + + public function rollBack(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"ROLLBACK"')); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + + if (null !== $query) { + $query->start(); + } + + try { + $ret = parent::rollBack(); + } finally { + if (null !== $query) { + $query->stop(); + } + + if (null !== $this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + + return $ret; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.php new file mode 100644 index 0000000000000..2643cc7493830 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DebugDataHolder.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\Bridge\Doctrine\Middleware\Debug; + +/** + * @author Laurent VOULLEMIER + */ +class DebugDataHolder +{ + private $data = []; + + public function addQuery(string $connectionName, Query $query): void + { + $this->data[$connectionName][] = [ + 'sql' => $query->getSql(), + 'params' => $query->getParams(), + 'types' => $query->getTypes(), + 'executionMS' => [$query, 'getDuration'], // stop() may not be called at this point + ]; + } + + public function getData(): array + { + foreach ($this->data as $connectionName => $dataForConn) { + foreach ($dataForConn as $idx => $data) { + if (\is_callable($data['executionMS'])) { + $this->data[$connectionName][$idx]['executionMS'] = $data['executionMS'](); + } + } + } + + return $this->data; + } + + public function reset(): void + { + $this->data = []; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php new file mode 100644 index 0000000000000..7f7fdd3bf0d8d --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver as DriverInterface; +use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Driver extends AbstractDriverMiddleware +{ + private $debugDataHolder; + private $stopwatch; + private $connectionName; + + public function __construct(DriverInterface $driver, DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch, string $connectionName) + { + parent::__construct($driver); + + $this->debugDataHolder = $debugDataHolder; + $this->stopwatch = $stopwatch; + $this->connectionName = $connectionName; + } + + public function connect(array $params): Connection + { + return new Connection( + parent::connect($params), + $this->debugDataHolder, + $this->stopwatch, + $this->connectionName + ); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Middleware.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Middleware.php new file mode 100644 index 0000000000000..18f6a58d5e7a2 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Middleware.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver as DriverInterface; +use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * Middleware to collect debug data. + * + * @author Laurent VOULLEMIER + */ +final class Middleware implements MiddlewareInterface +{ + private $debugDataHolder; + private $stopwatch; + private $connectionName; + + public function __construct(DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch, string $connectionName = 'default') + { + $this->debugDataHolder = $debugDataHolder; + $this->stopwatch = $stopwatch; + $this->connectionName = $connectionName; + } + + public function wrap(DriverInterface $driver): DriverInterface + { + return new Driver($driver, $this->debugDataHolder, $this->stopwatch, $this->connectionName); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php new file mode 100644 index 0000000000000..d652f620ce2e8 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\ParameterType; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +class Query +{ + private $params = []; + private $types = []; + + private $start; + private $duration; + + private $sql; + + public function __construct(string $sql) + { + $this->sql = $sql; + } + + public function start(): void + { + $this->start = microtime(true); + } + + public function stop(): void + { + if (null !== $this->start) { + $this->duration = microtime(true) - $this->start; + } + } + + /** + * @param string|int $param + * @param string|int|float|bool|null $variable + */ + public function setParam($param, &$variable, int $type): void + { + // Numeric indexes start at 0 in profiler + $idx = \is_int($param) ? $param - 1 : $param; + + $this->params[$idx] = &$variable; + $this->types[$idx] = $type; + } + + /** + * @param string|int $param + * @param string|int|float|bool|null $value + */ + public function setValue($param, $value, int $type): void + { + // Numeric indexes start at 0 in profiler + $idx = \is_int($param) ? $param - 1 : $param; + + $this->params[$idx] = $value; + $this->types[$idx] = $type; + } + + /** + * @param array $values + */ + public function setValues(array $values): void + { + foreach ($values as $param => $value) { + $this->setValue($param, $value, ParameterType::STRING); + } + } + + public function getSql(): string + { + return $this->sql; + } + + /** + * @return array + */ + public function getParams(): array + { + return $this->params; + } + + /** + * @return array + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * Query duration in seconds. + */ + public function getDuration(): ?float + { + return $this->duration; + } + + public function __clone() + { + $copy = []; + foreach ($this->params as $param => $valueOrVariable) { + $copy[$param] = $valueOrVariable; + } + $this->params = $copy; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php new file mode 100644 index 0000000000000..e52530e906dc2 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug; + +use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; +use Doctrine\DBAL\Driver\Result as ResultInterface; +use Doctrine\DBAL\Driver\Statement as StatementInterface; +use Doctrine\DBAL\ParameterType; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Statement extends AbstractStatementMiddleware +{ + private $debugDataHolder; + private $connectionName; + private $query; + + public function __construct(StatementInterface $statement, DebugDataHolder $debugDataHolder, string $connectionName, string $sql) + { + parent::__construct($statement); + + $this->debugDataHolder = $debugDataHolder; + $this->connectionName = $connectionName; + $this->query = new Query($sql); + } + + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + $this->query->setParam($param, $variable, $type); + + return parent::bindParam($param, $variable, $type, ...\array_slice(\func_get_args(), 3)); + } + + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + $this->query->setValue($param, $value, $type); + + return parent::bindValue($param, $value, $type); + } + + public function execute($params = null): ResultInterface + { + if (null !== $params) { + $this->query->setValues($params); + } + + // clone to prevent variables by reference to change + $this->debugDataHolder->addQuery($this->connectionName, $query = clone $this->query); + + $query->start(); + + try { + $result = parent::execute($params); + } finally { + $query->stop(); + } + + return $result; + } +} diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index c7e51cb172105..2126dad1bade6 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -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 8d497086a1422..4d8dcb260467d 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -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/Tests/DataCollector/DoctrineDataCollectorTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php index 35fc48ff1536f..25cc33fb4ae9f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php @@ -12,11 +12,14 @@ namespace Symfony\Bridge\Doctrine\Tests\DataCollector; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Logging\DebugStack; +use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\Persistence\ManagerRegistry; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; +use Symfony\Bridge\Doctrine\Middleware\Debug\Query; +use Symfony\Bridge\PhpUnit\ClockMock; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\VarDumper\Cloner\Data; @@ -27,66 +30,40 @@ class_exists(\Doctrine\DBAL\Platforms\MySqlPlatform::class); class DoctrineDataCollectorTest extends TestCase { - public function testCollectConnections() - { - $c = $this->createCollector([]); - $c->collect(new Request(), new Response()); - $this->assertEquals(['default' => 'doctrine.dbal.default_connection'], $c->getConnections()); - } - - public function testCollectManagers() - { - $c = $this->createCollector([]); - $c->collect(new Request(), new Response()); - $this->assertEquals(['default' => 'doctrine.orm.default_entity_manager'], $c->getManagers()); - } + use DoctrineDataCollectorTestTrait; - public function testCollectQueryCount() + protected function setUp(): void { - $c = $this->createCollector([]); - $c->collect(new Request(), new Response()); - $this->assertEquals(0, $c->getQueryCount()); - - $queries = [ - ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 0], - ]; - $c = $this->createCollector($queries); - $c->collect(new Request(), new Response()); - $this->assertEquals(1, $c->getQueryCount()); + ClockMock::register(self::class); + ClockMock::withClockMock(1500000000); } - public function testCollectTime() + public function testReset() { - $c = $this->createCollector([]); - $c->collect(new Request(), new Response()); - $this->assertEquals(0, $c->getTime()); - $queries = [ ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], ]; $c = $this->createCollector($queries); $c->collect(new Request(), new Response()); - $this->assertEquals(1, $c->getTime()); - $queries = [ - ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], - ['sql' => 'SELECT * FROM table2', 'params' => [], 'types' => [], 'executionMS' => 2], - ]; - $c = $this->createCollector($queries); + $c->reset(); $c->collect(new Request(), new Response()); - $this->assertEquals(3, $c->getTime()); + $c = unserialize(serialize($c)); + + $this->assertEquals([], $c->getQueries()); } /** * @dataProvider paramProvider */ - public function testCollectQueries($param, $types, $expected, $explainable, bool $runnable = true) + public function testCollectQueries($param, $types, $expected) { $queries = [ ['sql' => 'SELECT * FROM table1 WHERE field1 = ?1', 'params' => [$param], 'types' => $types, 'executionMS' => 1], ]; $c = $this->createCollector($queries); $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); $collectedQueries = $c->getQueries(); @@ -102,8 +79,8 @@ public function testCollectQueries($param, $types, $expected, $explainable, bool $this->assertEquals($expected, $collectedParam); } - $this->assertEquals($explainable, $collectedQueries['default'][0]['explainable']); - $this->assertSame($runnable, $collectedQueries['default'][0]['runnable']); + $this->assertTrue($collectedQueries['default'][0]['explainable']); + $this->assertTrue($collectedQueries['default'][0]['runnable']); } public function testCollectQueryWithNoParams() @@ -114,6 +91,7 @@ public function testCollectQueryWithNoParams() ]; $c = $this->createCollector($queries); $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); $collectedQueries = $c->getQueries(); $this->assertInstanceOf(Data::class, $collectedQueries['default'][0]['params']); @@ -126,36 +104,10 @@ public function testCollectQueryWithNoParams() $this->assertTrue($collectedQueries['default'][1]['runnable']); } - public function testCollectQueryWithNoTypes() - { - $queries = [ - ['sql' => 'SET sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))', 'params' => [], 'types' => null, 'executionMS' => 1], - ]; - $c = $this->createCollector($queries); - $c->collect(new Request(), new Response()); - - $collectedQueries = $c->getQueries(); - $this->assertSame([], $collectedQueries['default'][0]['types']); - } - - public function testReset() - { - $queries = [ - ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], - ]; - $c = $this->createCollector($queries); - $c->collect(new Request(), new Response()); - - $c->reset(); - $c->collect(new Request(), new Response()); - - $this->assertEquals(['default' => []], $c->getQueries()); - } - /** * @dataProvider paramProvider */ - public function testSerialization($param, array $types, $expected, $explainable, bool $runnable = true) + public function testSerialization($param, array $types, $expected) { $queries = [ ['sql' => 'SELECT * FROM table1 WHERE field1 = ?1', 'params' => [$param], 'types' => $types, 'executionMS' => 1], @@ -178,55 +130,17 @@ public function testSerialization($param, array $types, $expected, $explainable, $this->assertEquals($expected, $collectedParam); } - $this->assertEquals($explainable, $collectedQueries['default'][0]['explainable']); - $this->assertSame($runnable, $collectedQueries['default'][0]['runnable']); + $this->assertTrue($collectedQueries['default'][0]['explainable']); + $this->assertTrue($collectedQueries['default'][0]['runnable']); } public function paramProvider(): array { return [ - ['some value', [], 'some value', true], - [1, [], 1, true], - [true, [], true, true], - [null, [], null, true], - [new \DateTime('2011-09-11'), ['date'], '2011-09-11', true], - [fopen(__FILE__, 'r'), [], '/* Resource(stream) */', false, false], - [ - new \stdClass(), - [], - <<method('getConnection') ->willReturn($connection); - $logger = $this->createMock(DebugStack::class); - $logger->queries = $queries; + $debugDataHolder = new DebugDataHolder(); + $collector = new DoctrineDataCollector($registry, $debugDataHolder); + foreach ($queries as $queryData) { + $query = new Query($queryData['sql'] ?? ''); + foreach (($queryData['params'] ?? []) as $key => $value) { + if (\is_int($key)) { + ++$key; + } - $collector = new DoctrineDataCollector($registry); - $collector->addLogger('default', $logger); + $query->setValue($key, $value, $queryData['type'][$key] ?? ParameterType::STRING); + } - return $collector; - } -} + $query->start(); -class StringRepresentableClass -{ - public function __toString(): string - { - return 'string representation'; + $debugDataHolder->addQuery('default', $query); + + if (isset($queryData['executionMS'])) { + sleep($queryData['executionMS']); + } + $query->stop(); + } + + return $collector; } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.php new file mode 100644 index 0000000000000..23977a3be9881 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTestTrait.php @@ -0,0 +1,79 @@ +createCollector([]); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(['default' => 'doctrine.dbal.default_connection'], $c->getConnections()); + } + + public function testCollectManagers() + { + $c = $this->createCollector([]); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(['default' => 'doctrine.orm.default_entity_manager'], $c->getManagers()); + } + + public function testCollectQueryCount() + { + $c = $this->createCollector([]); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(0, $c->getQueryCount()); + + $queries = [ + ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 0], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(1, $c->getQueryCount()); + } + + public function testCollectTime() + { + $c = $this->createCollector([]); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(0, $c->getTime()); + + $queries = [ + ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(1, $c->getTime()); + + $queries = [ + ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], + ['sql' => 'SELECT * FROM table2', 'params' => [], 'types' => [], 'executionMS' => 2], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + $this->assertEquals(3, $c->getTime()); + } + + public function testCollectQueryWithNoTypes() + { + $queries = [ + ['sql' => 'SET sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))', 'params' => [], 'types' => null, 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + + $collectedQueries = $c->getQueries(); + $this->assertSame([], $collectedQueries['default'][0]['types']); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php new file mode 100644 index 0000000000000..f0962eff3132d --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php @@ -0,0 +1,195 @@ + + * + * 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 Doctrine\DBAL\Connection; +use Doctrine\DBAL\Logging\DebugStack; +use Doctrine\DBAL\Platforms\MySQLPlatform; +use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +// Doctrine DBAL 2 compatibility +class_exists(\Doctrine\DBAL\Platforms\MySqlPlatform::class); + +/** + * @group legacy + */ +class DoctrineDataCollectorWithDebugStackTest extends TestCase +{ + use DoctrineDataCollectorTestTrait; + + public function testReset() + { + $queries = [ + ['sql' => 'SELECT * FROM table1', 'params' => [], 'types' => [], 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + + $c->reset(); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + + $this->assertEquals(['default' => []], $c->getQueries()); + } + + /** + * @dataProvider paramProvider + */ + public function testCollectQueries($param, $types, $expected, $explainable, bool $runnable = true) + { + $queries = [ + ['sql' => 'SELECT * FROM table1 WHERE field1 = ?1', 'params' => [$param], 'types' => $types, 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + + $collectedQueries = $c->getQueries(); + + $collectedParam = $collectedQueries['default'][0]['params'][0]; + if ($collectedParam instanceof Data) { + $dumper = new CliDumper($out = fopen('php://memory', 'r+')); + $dumper->setColors(false); + $collectedParam->dump($dumper); + $this->assertStringMatchesFormat($expected, print_r(stream_get_contents($out, -1, 0), true)); + } elseif (\is_string($expected)) { + $this->assertStringMatchesFormat($expected, $collectedParam); + } else { + $this->assertEquals($expected, $collectedParam); + } + + $this->assertEquals($explainable, $collectedQueries['default'][0]['explainable']); + $this->assertSame($runnable, $collectedQueries['default'][0]['runnable']); + } + + /** + * @dataProvider paramProvider + */ + public function testSerialization($param, array $types, $expected, $explainable, bool $runnable = true) + { + $queries = [ + ['sql' => 'SELECT * FROM table1 WHERE field1 = ?1', 'params' => [$param], 'types' => $types, 'executionMS' => 1], + ]; + $c = $this->createCollector($queries); + $c->collect(new Request(), new Response()); + $c = unserialize(serialize($c)); + + $collectedQueries = $c->getQueries(); + + $collectedParam = $collectedQueries['default'][0]['params'][0]; + if ($collectedParam instanceof Data) { + $dumper = new CliDumper($out = fopen('php://memory', 'r+')); + $dumper->setColors(false); + $collectedParam->dump($dumper); + $this->assertStringMatchesFormat($expected, print_r(stream_get_contents($out, -1, 0), true)); + } elseif (\is_string($expected)) { + $this->assertStringMatchesFormat($expected, $collectedParam); + } else { + $this->assertEquals($expected, $collectedParam); + } + + $this->assertEquals($explainable, $collectedQueries['default'][0]['explainable']); + $this->assertSame($runnable, $collectedQueries['default'][0]['runnable']); + } + + public function paramProvider(): array + { + return [ + ['some value', [], 'some value', true], + [1, [], 1, true], + [true, [], true, true], + [null, [], null, true], + [new \DateTime('2011-09-11'), ['date'], '2011-09-11', true], + [fopen(__FILE__, 'r'), [], '/* Resource(stream) */', false, false], + [ + new \stdClass(), + [], + <<getMockBuilder(Connection::class) + ->disableOriginalConstructor() + ->getMock(); + $connection->expects($this->any()) + ->method('getDatabasePlatform') + ->willReturn(new MySqlPlatform()); + + $registry = $this->createMock(ManagerRegistry::class); + $registry + ->expects($this->any()) + ->method('getConnectionNames') + ->willReturn(['default' => 'doctrine.dbal.default_connection']); + $registry + ->expects($this->any()) + ->method('getManagerNames') + ->willReturn(['default' => 'doctrine.orm.default_entity_manager']); + $registry->expects($this->any()) + ->method('getConnection') + ->willReturn($connection); + + $collector = new DoctrineDataCollector($registry); + $logger = $this->createMock(DebugStack::class); + $logger->queries = $queries; + $collector->addLogger('default', $logger); + + return $collector; + } +} + +class StringRepresentableClass +{ + public function __toString(): string + { + return 'string representation'; + } +} 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 new file mode 100644 index 0000000000000..46e85784e821b --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php @@ -0,0 +1,256 @@ +markTestSkipped(sprintf('%s needed to run this test', MiddlewareInterface::class)); + } + + ClockMock::withClockMock(false); + } + + private function init(bool $withStopwatch = true): void + { + $this->stopwatch = $withStopwatch ? new Stopwatch() : null; + + $configuration = new Configuration(); + $this->debugDataHolder = new DebugDataHolder(); + $configuration->setMiddlewares([new Middleware($this->debugDataHolder, $this->stopwatch)]); + + $this->conn = DriverManager::getConnection([ + 'driver' => 'pdo_sqlite', + 'memory' => true, + ], $configuration); + + $this->conn->executeQuery(<< [ + static function(object $target, ...$args) { + return $target->executeStatement(...$args); + }, + ], + 'executeQuery' => [ + static function(object $target, ...$args): Result { + return $target->executeQuery(...$args); + }, + ], + ]; + } + + /** + * @dataProvider provideExecuteMethod + */ + public function testWithoutBinding(callable $executeMethod) + { + $this->init(); + + $executeMethod($this->conn, 'INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)'); + + $debug = $this->debugDataHolder->getData()['default'] ?? []; + $this->assertCount(2, $debug); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)', $debug[1]['sql']); + $this->assertSame([], $debug[1]['params']); + $this->assertSame([], $debug[1]['types']); + $this->assertGreaterThan(0, $debug[1]['executionMS']); + } + + /** + * @dataProvider provideExecuteMethod + */ + public function testWithValueBound(callable $executeMethod) + { + $this->init(); + + $stmt = $this->conn->prepare('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)'); + $stmt->bindValue(1, 'product1'); + $stmt->bindValue(2, 12.5); + $stmt->bindValue(3, 5, ParameterType::INTEGER); + + $executeMethod($stmt); + + $debug = $this->debugDataHolder->getData()['default'] ?? []; + $this->assertCount(2, $debug); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)', $debug[1]['sql']); + $this->assertSame(['product1', 12.5, 5], $debug[1]['params']); + $this->assertSame([ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER], $debug[1]['types']); + $this->assertGreaterThan(0, $debug[1]['executionMS']); + } + + /** + * @dataProvider provideExecuteMethod + */ + public function testWithParamBound(callable $executeMethod) + { + $this->init(); + + $product = 'product1'; + $price = 12.5; + $stock = 5; + + $stmt = $this->conn->prepare('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)'); + $stmt->bindParam(1, $product); + $stmt->bindParam(2, $price); + $stmt->bindParam(3, $stock, ParameterType::INTEGER); + + $executeMethod($stmt); + + // Debug data should not be affected by these changes + $product = 'product2'; + $price = 13.5; + $stock = 4; + + $debug = $this->debugDataHolder->getData()['default'] ?? []; + $this->assertCount(2, $debug); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES (?, ?, ?)', $debug[1]['sql']); + $this->assertSame(['product1', '12.5', 5], $debug[1]['params']); + $this->assertSame([ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER], $debug[1]['types']); + $this->assertGreaterThan(0, $debug[1]['executionMS']); + } + + public function provideEndTransactionMethod(): array + { + return [ + 'commit' => [ + static function(Connection $conn): bool { + return $conn->commit(); + }, + '"COMMIT"', + ], + 'rollback' => [ + static function(Connection $conn): bool { + return $conn->rollBack(); + }, + '"ROLLBACK"', + ], + ]; + } + + /** + * @dataProvider provideEndTransactionMethod + */ + public function testTransaction(callable $endTransactionMethod, string $expectedEndTransactionDebug) + { + $this->init(); + + $this->conn->beginTransaction(); + $this->conn->beginTransaction(); + $this->conn->executeStatement('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)'); + $endTransactionMethod($this->conn); + $endTransactionMethod($this->conn); + $this->conn->beginTransaction(); + $this->conn->executeStatement('INSERT INTO products(name, price, stock) VALUES ("product2", 15.5, 12)'); + $endTransactionMethod($this->conn); + + $debug = $this->debugDataHolder->getData()['default'] ?? []; + $this->assertCount(7, $debug); + $this->assertSame('"START TRANSACTION"', $debug[1]['sql']); + $this->assertGreaterThan(0, $debug[1]['executionMS']); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)', $debug[2]['sql']); + $this->assertGreaterThan(0, $debug[2]['executionMS']); + $this->assertSame($expectedEndTransactionDebug, $debug[3]['sql']); + $this->assertGreaterThan(0, $debug[3]['executionMS']); + $this->assertSame('"START TRANSACTION"', $debug[4]['sql']); + $this->assertGreaterThan(0, $debug[4]['executionMS']); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product2", 15.5, 12)', $debug[5]['sql']); + $this->assertGreaterThan(0, $debug[5]['executionMS']); + $this->assertSame($expectedEndTransactionDebug, $debug[6]['sql']); + $this->assertGreaterThan(0, $debug[6]['executionMS']); + } + + public function provideExecuteAndEndTransactionMethods(): array + { + return [ + 'commit and exec' => [ + static function(Connection $conn, string $sql) { + return $conn->executeStatement($sql); + }, + static function(Connection $conn): bool { + return $conn->commit(); + }, + ], + 'rollback and query' => [ + static function(Connection $conn, string $sql): Result { + return $conn->executeQuery($sql); + }, + static function(Connection $conn): bool { + return $conn->rollBack(); + }, + ], + ]; + } + + /** + * @dataProvider provideExecuteAndEndTransactionMethods + */ + public function testGlobalDoctrineDuration(callable $sqlMethod, callable $endTransactionMethod) + { + $this->init(); + + $periods = $this->stopwatch->getEvent('doctrine')->getPeriods(); + $this->assertCount(1, $periods); + + $this->conn->beginTransaction(); + + $this->assertFalse($this->stopwatch->getEvent('doctrine')->isStarted()); + $this->assertCount(2, $this->stopwatch->getEvent('doctrine')->getPeriods()); + + $sqlMethod($this->conn, 'SELECT * FROM products'); + + $this->assertFalse($this->stopwatch->getEvent('doctrine')->isStarted()); + $this->assertCount(3, $this->stopwatch->getEvent('doctrine')->getPeriods()); + + $endTransactionMethod($this->conn); + + $this->assertFalse($this->stopwatch->getEvent('doctrine')->isStarted()); + $this->assertCount(4, $this->stopwatch->getEvent('doctrine')->getPeriods()); + } + + /** + * @dataProvider provideExecuteAndEndTransactionMethods + */ + public function testWithoutStopwatch(callable $sqlMethod, callable $endTransactionMethod) + { + $this->init(false); + + $this->conn->beginTransaction(); + $sqlMethod($this->conn, 'SELECT * FROM products'); + $endTransactionMethod($this->conn); + } +} 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/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 37155c1fe4532..240e2eb976194 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -13,9 +13,9 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Types\Type; +use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Tools\SchemaTool; use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectRepository; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; @@ -111,7 +111,7 @@ protected function createEntityManagerMock($repositoryMock) ->willReturn($repositoryMock) ; - $classMetadata = $this->createMock(ClassMetadata::class); + $classMetadata = $this->createMock(ClassMetadataInfo::class); $classMetadata ->expects($this->any()) ->method('hasField') diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php index 0bb9591e185df..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')) { @@ -162,7 +159,8 @@ public function testExtractEnum() $validator = Validation::createValidatorBuilder() ->addMethodMapping('loadValidatorMetadata') - ->enableAnnotationMapping() + ->enableAnnotationMapping(true) + ->addDefaultDoctrineAnnotationReader() ->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{^Symfony\\\\Bridge\\\\Doctrine\\\\Tests\\\\Fixtures\\\\DoctrineLoader}')) ->getValidator() ; 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/DoctrineLoader.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php index 966d5c8af6dc7..45758d74b8685 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php @@ -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/Doctrine/phpunit.xml.dist b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist index 34c19c68c319d..f99086654ecab 100644 --- a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist +++ b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist @@ -28,4 +28,14 @@ ./vendor + + + + + + Symfony\Bridge\Doctrine\Middleware\Debug + + + + diff --git a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php index 5778dbb3b37c0..5b04c4a62434f 100644 --- a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php +++ b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php @@ -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/Handler/ElasticsearchLogstashHandler.php b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php index 65486f6b9d444..86af1d21bd02d 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php @@ -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/MailerHandler.php b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php index a7610f337be2a..f0446b09f3169 100644 --- a/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php @@ -32,7 +32,7 @@ public function __construct(MailerInterface $mailer, callable|Email $messageTemp 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 9355ee23457f5..a129085c905e5 100644 --- a/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php @@ -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/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..e7c7f5c82a085 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($line)[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,7 +304,7 @@ 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)); } } @@ -276,6 +314,7 @@ public static function fromUrlEncodedString($serializedConfiguration) 'disabled' => false, 'verbose' => true, 'quiet' => [], + 'ignoreFile' => '', 'generateBaseline' => false, 'baselineFile' => '', 'logFile' => null, @@ -300,6 +339,7 @@ public static function fromUrlEncodedString($serializedConfiguration) $normalizedConfiguration['max'] ?? [], '', $verboseOutput, + $normalizedConfiguration['ignoreFile'], filter_var($normalizedConfiguration['generateBaseline'], \FILTER_VALIDATE_BOOLEAN), $normalizedConfiguration['baselineFile'], $normalizedConfiguration['logFile'] 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..7192af3e23425 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ignore.phpt @@ -0,0 +1,77 @@ +--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/expectdeprecationfail.phpt b/src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt index 9f9bf8c17508e..f968cd188a0a7 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/expectdeprecationfail.phpt @@ -6,7 +6,7 @@ $test = realpath(__DIR__.'/FailTests/ExpectDeprecationTraitTestFail.php'); passthru('php '.getenv('SYMFONY_SIMPLE_PHPUNIT_BIN_DIR').'/simple-phpunit.php --colors=never '.$test); ?> --EXPECTF-- -PHPUnit %s by Sebastian Bergmann and contributors. +PHPUnit %s %ATesting Symfony\Bridge\PhpUnit\Tests\FailTests\ExpectDeprecationTraitTestFail FF 2 / 2 (100%) diff --git a/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt b/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt index 608c56488979f..91e0830553950 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt @@ -8,7 +8,7 @@ $test = realpath(__DIR__.'/FailTests/NoAssertionsTestRisky.php'); passthru('php '.getenv('SYMFONY_SIMPLE_PHPUNIT_BIN_DIR').'/simple-phpunit.php --fail-on-risky --colors=never '.$test); ?> --EXPECTF-- -PHPUnit %s by Sebastian Bergmann and contributors. +PHPUnit %s %ATesting Symfony\Bridge\PhpUnit\Tests\FailTests\NoAssertionsTestRisky R. 2 / 2 (100%) diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index a63cbb4667549..e787da909bbb3 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -185,6 +185,7 @@ function ($definition) { } } +#[\AllowDynamicProperties] final class DummyClass implements DummyInterface, SunnyInterface { private $ref; 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 b46646b5461ae..43d6e3ffc9056 100644 --- a/src/Symfony/Bridge/Twig/AppVariable.php +++ b/src/Symfony/Bridge/Twig/AppVariable.php @@ -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 542ec78218d4d..d5e64859a6c20 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -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 dfe66054bc496..d5ec05ea7f898 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -39,14 +39,16 @@ #[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')] class LintCommand extends Command { + protected string|array $namePatterns; private Environment $twig; private string $format; - public function __construct(Environment $twig) + public function __construct(Environment $twig, string|array $namePatterns = ['*.twig']) { parent::__construct(); $this->twig = $twig; + $this->namePatterns = $namePatterns; } protected function configure() @@ -146,7 +148,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 +174,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) diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php index f41aa479cb794..a26a620cfddb0 100644 --- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php +++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php @@ -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 ef3d433b94ce9..4dde73f62c902 100644 --- a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php +++ b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php @@ -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 35c69b6988910..3e12371ba99c3 100644 --- a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php @@ -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/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php index 1ce6593a13ec0..910fece83b9bb 100644 --- a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php @@ -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 6e42928a71c82..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; @@ -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 365e11733c92b..104bd3231f773 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php @@ -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/LogoutUrlExtension.php b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php index 5b29b4896906e..3ac7582b8fbeb 100644 --- a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php @@ -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/RoutingExtension.php b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php index 26591fd5533b5..3b3a2ac58e27b 100644 --- a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php @@ -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 3f24b82de5a17..dd1c57fb3b36e 100644 --- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php @@ -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/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php index 8371291d04f85..cbb7394e7d848 100644 --- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php @@ -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 de2884a71b987..fcc34ecb5aad6 100644 --- a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php @@ -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 2b13d29195afe..895faf457f648 100644 --- a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @@ -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/Mime/BodyRenderer.php b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php index e1b27046805a7..85e0ba93d42c9 100644 --- a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php +++ b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php @@ -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 084dc3489f270..60a29304c9b20 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -44,7 +44,7 @@ public function __construct(Headers $headers = null, AbstractPart $body = null) { $missingPackages = []; if (!class_exists(CssInlinerExtension::class)) { - $missingPackages['twig/cssinliner-extra'] = ' CSS Inliner'; + $missingPackages['twig/cssinliner-extra'] = 'CSS Inliner'; } if (!class_exists(InkyExtension::class)) { @@ -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/Resources/views/Form/bootstrap_3_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig index 34cbc76074acd..865f9078a9658 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig @@ -101,7 +101,22 @@ {%- endif -%} {%- endif -%} - {{- widget|raw }} {{ label is not same as(false) ? (translation_domain is same as(false) ? (label_html is same as(false) ? label : label|raw) : (label_html is same as(false) ? label|trans(label_translation_parameters, translation_domain) : label|trans(label_translation_parameters, translation_domain)|raw)) -}} + {#- if statement must be kept on the same line, to force the space between widget and label -#} + {{- widget|raw }} {% if label is not same as(false) -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{ label -}} + {%- else -%} + {{ label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{ label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{ label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + {%- endif -%} {%- endif -%} {%- endblock checkbox_radio_label %} 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/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/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 b53ab80df2747..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; @@ -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 7c5a2c90398cd..a5804bcb15913 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -7,6 +7,8 @@ CHANGELOG * 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 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 6bc39acc27923..2fd5f661d2dee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php @@ -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/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/ValidatorCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php index e28e21eb98324..04025a49bba66 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php @@ -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 4bdf3bed59485..aaad074daf246 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -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/BuildDebugContainerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php index 440c4762a95e9..785027dbc8d4e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php @@ -53,6 +53,10 @@ protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilde (new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); $locatorPass = new ServiceLocatorTagPass(); $locatorPass->process($container); + + $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); + $container->getCompilerPassConfig()->setOptimizationPasses([]); + $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); } return $this->containerBuilder = $container; 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 bb595e305f591..5e3e891ee845f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php @@ -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 = []; @@ -113,6 +112,10 @@ private function getContainerBuilder(): ContainerBuilder $skippedIds[$serviceId] = true; } } + + $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); + $container->getCompilerPassConfig()->setOptimizationPasses([]); + $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); } $container->setParameter('container.build_hash', 'lint_container'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php index 58e7ae1b437c1..ecd507127c00c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php @@ -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/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index 906f160b4f11f..c2cfae702863b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -131,7 +131,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $locale = $input->getArgument('locale'); $domain = $input->getOption('domain'); - $exitCode = 0; + $exitCode = self::SUCCESS; /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); @@ -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'); @@ -217,16 +217,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!$currentCatalogue->defines($messageId, $domain)) { $states[] = self::MESSAGE_MISSING; - $exitCode = $exitCode | self::EXIT_CODE_MISSING; + if (!$input->getOption('only-unused')) { + $exitCode = $exitCode | self::EXIT_CODE_MISSING; + } } } elseif ($currentCatalogue->defines($messageId, $domain)) { $states[] = self::MESSAGE_UNUSED; - $exitCode = $exitCode | self::EXIT_CODE_UNUSED; + if (!$input->getOption('only-missing')) { + $exitCode = $exitCode | self::EXIT_CODE_UNUSED; + } } - if (!\in_array(self::MESSAGE_UNUSED, $states) && true === $input->getOption('only-unused') - || !\in_array(self::MESSAGE_MISSING, $states) && true === $input->getOption('only-missing')) { + if (!\in_array(self::MESSAGE_UNUSED, $states) && $input->getOption('only-unused') + || !\in_array(self::MESSAGE_MISSING, $states) && $input->getOption('only-missing') + ) { continue; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 002a80e049b7c..ec399b0204c75 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -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 5f7cb53420d6f..8927eb1e07b1d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -145,7 +145,7 @@ public function all(string $namespace = null): array */ public function getLongVersion(): string { - return parent::getLongVersion().sprintf(' (env: %s, debug: %s)', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); + return parent::getLongVersion().sprintf(' (env: %s, debug: %s) #StandWithUkraine https://sf.to/ukraine', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); } public function add(Command $command): ?Command 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 89f407765b10c..ae5188b53aae5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -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/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php index 4f09e52bdcbd1..a0581ff21fb6f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -28,14 +29,18 @@ public function process(ContainerBuilder $container) // "annotation_reader" at build time don't get any cache foreach ($container->findTaggedServiceIds('annotations.cached_reader') as $id => $tags) { $reader = $container->getDefinition($id); + $reader->setPublic(false); $properties = $reader->getProperties(); if (isset($properties['cacheProviderBackup'])) { $provider = $properties['cacheProviderBackup']->getValues()[0]; unset($properties['cacheProviderBackup']); $reader->setProperties($properties); - $container->set($id, null); - $container->setDefinition($id, $reader->replaceArgument(1, $provider)); + $reader->replaceArgument(1, $provider); + } elseif (4 <= \count($arguments = $reader->getArguments()) && $arguments[3] instanceof ServiceClosureArgument) { + $arguments[1] = $arguments[3]->getValues()[0]; + unset($arguments[3]); + $reader->setArguments($arguments); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigureLocaleSwitcherPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigureLocaleSwitcherPass.php new file mode 100644 index 0000000000000..78f633bfcb5d2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigureLocaleSwitcherPass.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Kevin Bond + * + * @internal + */ +class ConfigureLocaleSwitcherPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->has('translation.locale_switcher')) { + return; + } + + $localeAwareServices = array_map( + fn (string $id) => new Reference($id), + array_keys($container->findTaggedServiceIds('kernel.locale_aware')) + ); + + if ($container->has('translation.locale_aware_request_context')) { + $localeAwareServices[] = new Reference('translation.locale_aware_request_context'); + } + + $container->getDefinition('translation.locale_switcher') + ->setArgument(1, new IteratorArgument($localeAwareServices)) + ; + } +} 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/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 5f2eb5daa173e..953fa3db97806 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -16,6 +16,7 @@ use Psr\Log\LogLevel; use Symfony\Bundle\FullStack; use Symfony\Component\Asset\Package; +use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder; @@ -1049,7 +1050,7 @@ private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBe ->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end() ->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end() ->scalarNode('default_doctrine_dbal_provider')->defaultValue('database_connection')->end() - ->scalarNode('default_pdo_provider')->defaultValue($willBeAvailable('doctrine/dbal', Connection::class) ? 'database_connection' : null)->end() + ->scalarNode('default_pdo_provider')->defaultValue($willBeAvailable('doctrine/dbal', Connection::class) && class_exists(DoctrineAdapter::class) ? 'database_connection' : null)->end() ->arrayNode('pools') ->useAttributeAsKey('name') ->prototype('array') @@ -1425,6 +1426,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.') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index b81e8d147f921..9aec80d8a96c4 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; @@ -80,6 +81,7 @@ 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; @@ -117,6 +119,7 @@ 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; @@ -144,6 +147,7 @@ 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; @@ -163,6 +167,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; @@ -182,6 +187,9 @@ use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; 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; @@ -229,6 +237,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; @@ -241,9 +250,13 @@ 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 (\PHP_VERSION_ID < 80100 || !class_exists(BackedEnumValueResolver::class)) { + if (!class_exists(BackedEnumValueResolver::class)) { $container->removeDefinition('argument_resolver.backed_enum_resolver'); } @@ -374,7 +387,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".'); } @@ -412,6 +425,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 @@ -502,7 +517,7 @@ 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); $this->addAnnotatedClassesToCompile([ @@ -510,7 +525,7 @@ public function load(array $configs, ContainerBuilder $container) '**\\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'])) { @@ -609,11 +624,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); }); @@ -779,6 +799,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']); @@ -1044,6 +1068,7 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co $container->removeDefinition('console.command.router_debug'); $container->removeDefinition('console.command.router_match'); $container->removeDefinition('messenger.middleware.router_context'); + $container->removeDefinition('translation.locale_aware_request_context'); return; } @@ -1603,9 +1628,10 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde $container ->getDefinition('annotations.cached_reader') + ->setPublic(true) // set to false in AddAnnotationsCachedReaderPass ->replaceArgument(2, $config['debug']) - // temporary property to lazy-reference the cache provider without using it until AddAnnotationsCachedReaderPass runs - ->setProperty('cacheProviderBackup', new ServiceClosureArgument(new Reference($cacheService))) + // reference the cache provider without using it until AddAnnotationsCachedReaderPass runs + ->addArgument(new ServiceClosureArgument(new Reference($cacheService))) ->addTag('annotations.cached_reader') ; @@ -1731,7 +1757,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); @@ -1740,7 +1766,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; }; @@ -1813,7 +1839,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]); } @@ -2431,6 +2457,7 @@ 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', @@ -2458,6 +2485,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ 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', @@ -2573,6 +2601,10 @@ 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 resolveTrustedHeaders(array $headers): int diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 794686e3cdf5f..fe0bf8f5465a7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AssetsContextPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigureLocaleSwitcherPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilderDebugDumpPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass; @@ -158,6 +159,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new RegisterReverseContainerPass(true)); $container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new RemoveUnusedSessionMarshallingHandlerPass()); + $container->addCompilerPass(new ConfigureLocaleSwitcherPass()); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 2); diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index 6795e3c22aa0e..c80177c5d39c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -156,6 +156,7 @@ protected function doRequest(object $request): Response // avoid shutting down the Kernel if no request has been performed yet // WebTestCase::createClient() boots the Kernel but do not handle a request if ($this->hasPerformedRequest && $this->reboot) { + $this->kernel->boot(); $this->kernel->shutdown(); } else { $this->hasPerformedRequest = true; 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 647d056406c6e..819177c337787 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -15,6 +15,7 @@ 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; @@ -42,6 +43,7 @@ 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; @@ -155,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') @@ -256,5 +262,9 @@ ->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/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/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..79a7e9dce89e3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php @@ -13,6 +13,7 @@ use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer; +use Symfony\Bundle\FrameworkBundle\Translation\LocaleAwareRequestContext; use Symfony\Bundle\FrameworkBundle\Translation\Translator; use Symfony\Component\Translation\Dumper\CsvFileDumper; use Symfony\Component\Translation\Dumper\IcuResFileDumper; @@ -39,6 +40,7 @@ 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; @@ -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']) @@ -159,4 +165,21 @@ ->tag('container.service_subscriber', ['id' => 'translator']) ->tag('kernel.cache_warmer') ; + + if (class_exists(LocaleSwitcher::class)) { + $container->services() + ->set('translation.locale_switcher', LocaleSwitcher::class) + ->args([ + param('kernel.default_locale'), + abstract_arg('LocaleAware services'), + ]) + ->alias(LocaleSwitcher::class, 'translation.locale_switcher') + + ->set('translation.locale_aware_request_context', LocaleAwareRequestContext::class) + ->args([ + service('router.request_context'), + param('kernel.default_locale'), + ]) + ; + } }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php index a7d91bfd4a69d..d28e88995f84c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php @@ -14,11 +14,13 @@ 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; @@ -26,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) { @@ -47,9 +48,13 @@ ]) ->set('argument_resolver.backed_enum_resolver', BackedEnumValueResolver::class) - ->tag('controller.argument_value_resolver', [ - 'priority' => 105, // prior to the RequestAttributeValueResolver - ]) + ->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]) @@ -79,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/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index b213019d3579a..8d3c33d64c3ee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -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 d68ae4c8b3ba5..7e9b90ef33d30 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -35,11 +35,10 @@ abstract class KernelTestCase extends TestCase protected static $booted = false; - private static ?ContainerInterface $kernelContainer = null; - protected function tearDown(): void { static::ensureKernelShutdown(); + static::$class = null; static::$kernel = null; static::$booted = false; } @@ -68,12 +67,11 @@ protected static function bootKernel(array $options = []): KernelInterface { static::ensureKernelShutdown(); - static::$kernel = static::createKernel($options); - static::$kernel->boot(); + $kernel = static::createKernel($options); + $kernel->boot(); + self::$kernel = $kernel; static::$booted = true; - self::$kernelContainer = static::$kernel->getContainer(); - return static::$kernel; } @@ -94,7 +92,7 @@ protected static function getContainer(): ContainerInterface } try { - return self::$kernelContainer->get('test.service_container'); + return self::$kernel->getContainer()->get('test.service_container'); } catch (ServiceNotFoundException $e) { throw new \LogicException('Could not find service "test.service_container". Try updating the "framework.test" config to "true".', 0, $e); } @@ -143,14 +141,14 @@ protected static function createKernel(array $options = []): KernelInterface protected static function ensureKernelShutdown() { if (null !== static::$kernel) { + static::$kernel->boot(); + $container = static::$kernel->getContainer(); static::$kernel->shutdown(); static::$booted = false; - } - if (self::$kernelContainer instanceof ResetInterface) { - self::$kernelContainer->reset(); + if ($container instanceof ResetInterface) { + $container->reset(); + } } - - self::$kernelContainer = null; } } 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..33ea1b357b466 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/AnnotationsCacheWarmerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/AnnotationsCacheWarmerTest.php @@ -151,10 +151,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/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/TranslationDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php index d755e11e730af..70f94d6a34d48 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php @@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\ExtensionWithoutConfigTestBundle\ExtensionWithoutConfigTestBundle; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\DependencyInjection\Container; @@ -36,7 +37,7 @@ public function testDebugMissingMessages() $res = $tester->execute(['locale' => 'en', 'bundle' => 'foo']); $this->assertMatchesRegularExpression('/missing/', $tester->getDisplay()); - $this->assertEquals(TranslationDebugCommand::EXIT_CODE_MISSING, $res); + $this->assertSame(TranslationDebugCommand::EXIT_CODE_MISSING, $res); } public function testDebugUnusedMessages() @@ -45,7 +46,7 @@ public function testDebugUnusedMessages() $res = $tester->execute(['locale' => 'en', 'bundle' => 'foo']); $this->assertMatchesRegularExpression('/unused/', $tester->getDisplay()); - $this->assertEquals(TranslationDebugCommand::EXIT_CODE_UNUSED, $res); + $this->assertSame(TranslationDebugCommand::EXIT_CODE_UNUSED, $res); } public function testDebugFallbackMessages() @@ -54,7 +55,7 @@ public function testDebugFallbackMessages() $res = $tester->execute(['locale' => 'fr', 'bundle' => 'foo']); $this->assertMatchesRegularExpression('/fallback/', $tester->getDisplay()); - $this->assertEquals(TranslationDebugCommand::EXIT_CODE_FALLBACK, $res); + $this->assertSame(TranslationDebugCommand::EXIT_CODE_FALLBACK, $res); } public function testNoDefinedMessages() @@ -63,7 +64,7 @@ public function testNoDefinedMessages() $res = $tester->execute(['locale' => 'fr', 'bundle' => 'test']); $this->assertMatchesRegularExpression('/No defined or extracted messages for locale "fr"/', $tester->getDisplay()); - $this->assertEquals(TranslationDebugCommand::EXIT_CODE_GENERAL_ERROR, $res); + $this->assertSame(TranslationDebugCommand::EXIT_CODE_GENERAL_ERROR, $res); } public function testDebugDefaultDirectory() @@ -74,7 +75,7 @@ public function testDebugDefaultDirectory() $this->assertMatchesRegularExpression('/missing/', $tester->getDisplay()); $this->assertMatchesRegularExpression('/unused/', $tester->getDisplay()); - $this->assertEquals($expectedExitStatus, $res); + $this->assertSame($expectedExitStatus, $res); } public function testDebugDefaultRootDirectory() @@ -92,7 +93,7 @@ public function testDebugDefaultRootDirectory() $this->assertMatchesRegularExpression('/missing/', $tester->getDisplay()); $this->assertMatchesRegularExpression('/unused/', $tester->getDisplay()); - $this->assertEquals($expectedExitStatus, $res); + $this->assertSame($expectedExitStatus, $res); } public function testDebugCustomDirectory() @@ -112,7 +113,7 @@ public function testDebugCustomDirectory() $this->assertMatchesRegularExpression('/missing/', $tester->getDisplay()); $this->assertMatchesRegularExpression('/unused/', $tester->getDisplay()); - $this->assertEquals($expectedExitStatus, $res); + $this->assertSame($expectedExitStatus, $res); } public function testDebugInvalidDirectory() @@ -128,6 +129,22 @@ public function testDebugInvalidDirectory() $tester->execute(['locale' => 'en', 'bundle' => 'dir']); } + public function testNoErrorWithOnlyMissingOptionAndNoResults() + { + $tester = $this->createCommandTester([], ['foo' => 'foo']); + $res = $tester->execute(['locale' => 'en', '--only-missing' => true]); + + $this->assertSame(Command::SUCCESS, $res); + } + + public function testNoErrorWithOnlyUnusedOptionAndNoResults() + { + $tester = $this->createCommandTester(['foo' => 'foo']); + $res = $tester->execute(['locale' => 'en', '--only-unused' => true]); + + $this->assertSame(Command::SUCCESS, $res); + } + protected function setUp(): void { $this->fs = new Filesystem(); 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/Compiler/ConfigureLocaleSwitcherPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigureLocaleSwitcherPassTest.php new file mode 100644 index 0000000000000..5a353ba460b1b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigureLocaleSwitcherPassTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigureLocaleSwitcherPass; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class ConfigureLocaleSwitcherPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->register('translation.locale_switcher')->setArgument(0, 'en'); + $container->register('locale_aware_service') + ->addTag('kernel.locale_aware') + ; + + $pass = new ConfigureLocaleSwitcherPass(); + $pass->process($container); + + $switcherDef = $container->getDefinition('translation.locale_switcher'); + + $this->assertInstanceOf(IteratorArgument::class, $switcherDef->getArgument(1)); + $this->assertEquals([new Reference('locale_aware_service')], $switcherDef->getArgument(1)->getValues()); + } + + public function testProcessWithRequestContext() + { + $container = new ContainerBuilder(); + $container->register('translation.locale_switcher'); + $container->register('locale_aware_service') + ->addTag('kernel.locale_aware') + ; + $container->register('translation.locale_aware_request_context'); + + $pass = new ConfigureLocaleSwitcherPass(); + $pass->process($container); + + $switcherDef = $container->getDefinition('translation.locale_switcher'); + + $this->assertInstanceOf(IteratorArgument::class, $switcherDef->getArgument(1)); + $this->assertEquals( + [ + new Reference('locale_aware_service'), + new Reference('translation.locale_aware_request_context'), + ], + $switcherDef->getArgument(1)->getValues() + ); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 5a769c85f5e5f..1b154cb76f2a9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Configuration; use Symfony\Bundle\FullStack; +use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -501,7 +502,7 @@ protected static function getBundleDefaultConfig() 'default_redis_provider' => 'redis://localhost', 'default_memcached_provider' => 'memcached://localhost', 'default_doctrine_dbal_provider' => 'database_connection', - 'default_pdo_provider' => ContainerBuilder::willBeAvailable('doctrine/dbal', Connection::class, ['symfony/framework-bundle']) ? 'database_connection' : null, + 'default_pdo_provider' => ContainerBuilder::willBeAvailable('doctrine/dbal', Connection::class, ['symfony/framework-bundle']) && class_exists(DoctrineAdapter::class) ? 'database_connection' : null, 'prefix_seed' => '_%kernel.project_dir%.%kernel.container_class%', ], 'workflows' => [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index ccd35edd68d08..ccddd03b96f4d 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; @@ -30,6 +31,7 @@ use Symfony\Component\Cache\Adapter\TagAwareAdapter; use Symfony\Component\Cache\DependencyInjection\CachePoolPass; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; @@ -65,6 +67,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; @@ -78,6 +81,8 @@ abstract class FrameworkExtensionTest extends TestCase { + use ExpectDeprecationTrait; + private static $containerCache = []; abstract protected function loadFromFile(ContainerBuilder $container, $file); @@ -709,6 +714,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'); @@ -969,6 +994,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'); @@ -1967,6 +2003,26 @@ 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')); + $this->assertTrue($container->has('translation.locale_aware_request_context')); + + $switcherDef = $container->getDefinition('translation.locale_switcher'); + $localeAwareRequestContextDef = $container->getDefinition('translation.locale_aware_request_context'); + + $this->assertSame('%kernel.default_locale%', $switcherDef->getArgument(0)); + $this->assertInstanceOf(AbstractArgument::class, $switcherDef->getArgument(1)); + $this->assertEquals(new Reference('router.request_context'), $localeAwareRequestContextDef->getArgument(0)); + $this->assertSame('%kernel.default_locale%', $localeAwareRequestContextDef->getArgument(1)); + } + protected function createContainer(array $data = []) { return new ContainerBuilder(new EnvPlaceholderParameterBag(array_merge([ 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/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/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/TestServiceContainerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php index 9108d47b244c8..76a373c0c05a9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php @@ -39,4 +39,22 @@ public function testThatPrivateServicesAreAvailableIfTestConfigIsEnabled() $this->assertTrue(static::getContainer()->has('private_service')); $this->assertFalse(static::getContainer()->has(UnusedPrivateService::class)); } + + /** + * @doesNotPerformAssertions + */ + public function testBootKernel() + { + static::bootKernel(['test_case' => 'TestServiceContainer']); + } + + /** + * @depends testBootKernel + */ + public function testKernelIsNotInitialized() + { + self::assertNull(self::$class); + self::assertNull(self::$kernel); + self::assertFalse(self::$booted); + } } 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/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/KernelBrowserTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/KernelBrowserTest.php index 2770813d8a696..404a239b51282 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/KernelBrowserTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/KernelBrowserTest.php @@ -51,10 +51,20 @@ public function testEnableRebootKernel() $client->request('GET', '/'); } + public function testRequestAfterKernelShutdownAndPerformedRequest() + { + $this->expectNotToPerformAssertions(); + + $client = static::createClient(['test_case' => 'TestServiceContainer']); + $client->request('GET', '/'); + static::ensureKernelShutdown(); + $client->request('GET', '/'); + } + private function getKernelMock() { $mock = $this->getMockBuilder($this->getKernelClass()) - ->setMethods(['shutdown', 'boot', 'handle']) + ->setMethods(['shutdown', 'boot', 'handle', 'getContainer']) ->disableOriginalConstructor() ->getMock(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php index a9b88b1763bd5..de096ccb22de0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php @@ -6,6 +6,9 @@ use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault; use Symfony\Component\Filesystem\Filesystem; +/** + * @requires extension sodium + */ class SodiumVaultTest extends TestCase { private $secretsDir; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/LocaleAwareRequestContextTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/LocaleAwareRequestContextTest.php new file mode 100644 index 0000000000000..f98454cb1136b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/LocaleAwareRequestContextTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Translation; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Translation\LocaleAwareRequestContext; +use Symfony\Component\Routing\RequestContext; + +class LocaleAwareRequestContextTest extends TestCase +{ + public function testCanSwitchLocale() + { + $context = new RequestContext(); + $service = new LocaleAwareRequestContext($context, 'en'); + + $this->assertSame('en', $service->getLocale()); + $this->assertNull($context->getParameter('_locale')); + + $service->setLocale('fr'); + + $this->assertSame('fr', $service->getLocale()); + $this->assertSame('fr', $context->getParameter('_locale')); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/LocaleAwareRequestContext.php b/src/Symfony/Bundle/FrameworkBundle/Translation/LocaleAwareRequestContext.php new file mode 100644 index 0000000000000..59e846b1b4ad4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/LocaleAwareRequestContext.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Translation; + +use Symfony\Component\Routing\RequestContext; +use Symfony\Contracts\Translation\LocaleAwareInterface; + +/** + * @author Kevin Bond + */ +final class LocaleAwareRequestContext implements LocaleAwareInterface +{ + public function __construct(private RequestContext $requestContext, private string $defaultLocale) + { + } + + public function setLocale(string $locale): void + { + $this->requestContext->setParameter('_locale', $locale); + } + + public function getLocale(): string + { + return $this->requestContext->getParameter('_locale') ?? $this->defaultLocale; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index f19610887be35..f915572ea730e 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" @@ -47,7 +47,7 @@ "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", @@ -62,11 +62,10 @@ "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", - "paragonie/sodium_compat": "^1.8", - "twig/twig": "^2.10|^3.0", - "symfony/phpunit-bridge": "^5.4|^6.0" + "twig/twig": "^2.10|^3.0" }, "conflict": { "doctrine/annotations": "<1.13.1", @@ -86,7 +85,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 3b3bb88afa573..35631064bacf3 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -3,7 +3,9 @@ CHANGELOG 6.1 --- -* The `security.access_control` now accepts a `RequestMatcherInterface` under the `request_matcher` option as scope configuration + + * 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/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index 394006f55b66b..c7df65486efdf 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -111,7 +111,7 @@ public function collect(Request $request, Response $response, \Throwable $except $logoutUrl = null; try { $logoutUrl = $this->logoutUrlGenerator?->getLogoutPath(); - } catch (\Exception $e) { + } catch (\Exception) { // fail silently when the logout URL cannot be generated } 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/Compiler/RegisterTokenUsageTrackingPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php index b26a32b5a7d6d..2b1159eff979a 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; -use Symfony\Bridge\Monolog\Processor\ProcessorInterface; +use Monolog\Processor\ProcessorInterface; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 9e8d0f81a7374..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; @@ -306,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)); } } @@ -842,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) ; @@ -881,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/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/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/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..f1699eab2f6cf 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 cd55a6ecd26ff..63d0227513845 100644 --- a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php +++ b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php @@ -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..90786764e0eeb 100644 --- a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php +++ b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php @@ -49,7 +49,7 @@ 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'); + return Finder::create()->files()->in($dir)->name($this->namePatterns); } 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..9d46c4fa0d15a 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -97,6 +97,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 d8a7ad67d6d68..7242cfb0c5d44 100644 --- a/src/Symfony/Bundle/TwigBundle/TemplateIterator.php +++ b/src/Symfony/Bundle/TwigBundle/TemplateIterator.php @@ -29,16 +29,19 @@ class TemplateIterator implements \IteratorAggregate 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/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index 6b73f21ec2b41..3cdec828217b5 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -81,7 +81,7 @@ public function panelAction(Request $request, string $token): Response } if (!$profile = $this->profiler->loadProfile($token)) { - return new Response($this->twig->render('@WebProfiler/Profiler/info.html.twig', ['about' => 'no_token', 'token' => $token, 'request' => $request]), 200, ['Content-Type' => 'text/html']); + return $this->renderWithCspNonces($request, '@WebProfiler/Profiler/info.html.twig', ['about' => 'no_token', 'token' => $token, 'request' => $request]); } if (null === $panel) { @@ -104,7 +104,7 @@ public function panelAction(Request $request, string $token): Response throw new NotFoundHttpException(sprintf('Panel "%s" is not available for token "%s".', $panel, $token)); } - return new Response($this->twig->render($this->getTemplateManager()->getName($profile, $panel), [ + return $this->renderWithCspNonces($request, $this->getTemplateManager()->getName($profile, $panel), [ 'token' => $token, 'profile' => $profile, 'collector' => $profile->getCollector($panel), @@ -114,7 +114,7 @@ public function panelAction(Request $request, string $token): Response 'templates' => $this->getTemplateManager()->getNames($profile), 'is_ajax' => $request->isXmlHttpRequest(), 'profiler_markup_version' => 2, // 1 = original profiler, 2 = Symfony 2.8+ profiler - ]), 200, ['Content-Type' => 'text/html']); + ]); } /** @@ -146,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 } @@ -232,7 +232,7 @@ public function searchResultsAction(Request $request, string $token): Response $end = $request->query->get('end', null); $limit = $request->query->get('limit'); - return new Response($this->twig->render('@WebProfiler/Profiler/results.html.twig', [ + return $this->renderWithCspNonces($request, '@WebProfiler/Profiler/results.html.twig', [ 'request' => $request, 'token' => $token, 'profile' => $profile, @@ -245,7 +245,7 @@ public function searchResultsAction(Request $request, string $token): Response 'end' => $end, 'limit' => $limit, 'panel' => null, - ]), 200, ['Content-Type' => 'text/html']); + ]); } /** @@ -359,11 +359,11 @@ public function openAction(Request $request): Response throw new NotFoundHttpException(sprintf('The file "%s" cannot be opened.', $file)); } - return new Response($this->twig->render('@WebProfiler/Profiler/open.html.twig', [ + return $this->renderWithCspNonces($request, '@WebProfiler/Profiler/open.html.twig', [ 'filename' => $filename, 'file' => $file, 'line' => $line, - ]), 200, ['Content-Type' => 'text/html']); + ]); } protected function getTemplateManager(): TemplateManager 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 85182fea2aad5..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 %} @@ -108,7 +108,7 @@ {% block menu %} - {{ include('@WebProfiler/Icon/config.svg') }} + {{ source('@WebProfiler/Icon/config.svg') }} Configuration {% endblock %} @@ -191,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 7b1a35b748dd7..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,14 +83,14 @@
- {{ include('@WebProfiler/Icon/filter.svg') }} + {{ source('@WebProfiler/Icon/filter.svg') }} Level ({{ filters.priority|length - 1 }})
- Select All - Select None + +
{% for label, value in filters.priority %} @@ -104,14 +104,14 @@
- {{ include('@WebProfiler/Icon/filter.svg') }} + {{ source('@WebProfiler/Icon/filter.svg') }} Channel ({{ filters.channel|length - 1 }})
- Select All - Select None + +
{% for value in filters.channel %} @@ -155,6 +155,10 @@ {{ log.type|lower }} {% endif %} + {% else %} + + {{ log.priorityName|lower }} + {% endif %} @@ -204,7 +208,7 @@ {% set context_id = 'context-compiler-' ~ loop.index %} - {{ class }} +
    @@ -243,12 +247,12 @@ {% if has_context %} {% set context_id = 'context-' ~ category ~ '-' ~ log_index %} - Show context + {% endif %} {% if has_trace %} {% set trace_id = 'trace-' ~ category ~ '-' ~ log_index %} - Show trace +
    {{ profiler_dump(log.context.exception.trace, maxDepth=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 fd92f10e408b2..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 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.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig index 0850414d8c4af..1a74431d898b5 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig @@ -8,13 +8,13 @@ {% block head %} - {% endblock %} -