diff --git a/.appveyor.yml b/.appveyor.yml index 5f9ba4577a64b..5474f89111c2a 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -62,9 +62,9 @@ test_script: - copy /Y c:\php\php.ini-min c:\php\php.ini - IF %APPVEYOR_REPO_BRANCH:~-2% neq .x (rm -Rf src\Symfony\Bridge\PhpUnit) - mv src\Symfony\Component\HttpClient\phpunit.xml.dist src\Symfony\Component\HttpClient\phpunit.xml - - php phpunit src\Symfony --exclude-group tty,benchmark,intl-data || SET X=!errorlevel! + - php phpunit src\Symfony --exclude-group tty,benchmark,intl-data,network,transient-on-windows || SET X=!errorlevel! - php phpunit src\Symfony\Component\HttpClient || SET X=!errorlevel! - copy /Y c:\php\php.ini-max c:\php\php.ini - - php phpunit src\Symfony --exclude-group tty,benchmark,intl-data || SET X=!errorlevel! + - php phpunit src\Symfony --exclude-group tty,benchmark,intl-data,network,transient-on-windows || SET X=!errorlevel! - php phpunit src\Symfony\Component\HttpClient || SET X=!errorlevel! - exit %X% diff --git a/.github/expected-missing-return-types.diff b/.github/expected-missing-return-types.diff index 038db511c4403..594290521dab2 100644 --- a/.github/expected-missing-return-types.diff +++ b/.github/expected-missing-return-types.diff @@ -6,7 +6,7 @@ head=$(sed '/^diff /Q' .github/expected-missing-return-types.diff) (echo "$head" && echo && git diff -U2 composer.json src/) > .github/expected-missing-return-types.diff diff --git a/composer.json b/composer.json -index 1bc576b112..589ef8c260 100644 +index 1750d55ee2..c18494167a 100644 --- a/composer.json +++ b/composer.json @@ -175,5 +175,5 @@ @@ -16,8 +16,19 @@ index 1bc576b112..589ef8c260 100644 + ] }, +diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +index d68ae4c8b3..8e980a9e70 100644 +--- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php ++++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +@@ -88,5 +88,5 @@ abstract class KernelTestCase extends TestCase + * @return TestContainer + */ +- protected static function getContainer(): ContainerInterface ++ protected static function getContainer(): TestContainer + { + if (!static::$booted) { diff --git a/src/Symfony/Component/BrowserKit/AbstractBrowser.php b/src/Symfony/Component/BrowserKit/AbstractBrowser.php -index 152050159b..e2ec1aeea2 100644 +index 697e34cb77..9a1e4c5618 100644 --- a/src/Symfony/Component/BrowserKit/AbstractBrowser.php +++ b/src/Symfony/Component/BrowserKit/AbstractBrowser.php @@ -408,5 +408,5 @@ abstract class AbstractBrowser @@ -144,7 +155,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 b582435f7d..3d5f064773 100644 +index 0ed5649b8a..6c814bf82c 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -218,5 +218,5 @@ class Application implements ResetInterface @@ -197,7 +208,7 @@ index b582435f7d..3d5f064773 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 d6354b4ab1..b267917312 100644 +index 0bd3426c07..b38caf989e 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -171,5 +171,5 @@ class Command @@ -250,10 +261,10 @@ index 024da1884e..943790e875 100644 /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php -index 1acec50de5..904e67a47b 100644 +index 326d8d4f5f..e62124c7af 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php -@@ -70,5 +70,5 @@ abstract class AbstractRecursivePass implements CompilerPassInterface +@@ -71,5 +71,5 @@ abstract class AbstractRecursivePass implements CompilerPassInterface * @return mixed */ - protected function processValue(mixed $value, bool $isRoot = false) @@ -261,7 +272,7 @@ index 1acec50de5..904e67a47b 100644 { if (\is_array($value)) { diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php -index 0532120adf..78fba5ef79 100644 +index b9dd838898..3a8cb63eac 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -108,5 +108,5 @@ class Container implements ContainerInterface, ResetInterface @@ -535,7 +546,7 @@ index 1cb865fd66..f6f4efe7a7 100644 + public function getName(): string; } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php -index 8e7b80a909..d757429d36 100644 +index cf0e243c6b..292b907ea8 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -448,5 +448,5 @@ class HttpCache implements HttpKernelInterface, TerminableInterface @@ -544,9 +555,9 @@ index 8e7b80a909..d757429d36 100644 - protected function forward(Request $request, bool $catch = false, Response $entry = null) + protected function forward(Request $request, bool $catch = false, Response $entry = null): Response { - if ($this->surrogate) { + $this->surrogate?->addSurrogateCapability($request); diff --git a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php -index f4773eb62a..0ba648b57f 100644 +index 3ecc2c739c..d50834b6d9 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php +++ b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php @@ -61,5 +61,5 @@ class HttpKernelBrowser extends AbstractBrowser @@ -581,8 +592,25 @@ index 19ff0db181..f0f4a5829f 100644 + public function countErrors(Request $request = null): int; /** +diff --git a/src/Symfony/Component/Messenger/Envelope.php b/src/Symfony/Component/Messenger/Envelope.php +index bb1194ad1b..bd7fc440ff 100644 +--- a/src/Symfony/Component/Messenger/Envelope.php ++++ b/src/Symfony/Component/Messenger/Envelope.php +@@ -13,4 +13,5 @@ namespace Symfony\Component\Messenger; + + use Symfony\Component\Messenger\Stamp\StampInterface; ++use Symfony\Component\Messenger\Stamp\StampInterface as TStamp; + + /** +@@ -101,5 +102,5 @@ final class Envelope + * @return TStamp|null + */ +- public function last(string $stampFqcn): ?StampInterface ++ public function last(string $stampFqcn): ?TStamp + { + return isset($this->stamps[$stampFqcn]) ? end($this->stamps[$stampFqcn]) : null; diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php -index fe77644762..09dcfe166b 100644 +index 205c15b4cd..e93e460ed1 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -486,5 +486,5 @@ class OptionsResolver implements Options @@ -858,7 +886,7 @@ index b0a1f3218b..893fe59e2f 100644 { if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php -index a241215133..2ed1af7a02 100644 +index 3e1e7edc8e..e025b2bc4a 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -136,5 +136,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer @@ -1002,3 +1030,35 @@ index d6dcdf178f..0ab8d9c10e 100644 + public function getTargets(): string|array { return self::PROPERTY_CONSTRAINT; +diff --git a/src/Symfony/Component/Workflow/Event/Event.php b/src/Symfony/Component/Workflow/Event/Event.php +index cd7fab7896..b340eba38e 100644 +--- a/src/Symfony/Component/Workflow/Event/Event.php ++++ b/src/Symfony/Component/Workflow/Event/Event.php +@@ -42,5 +42,5 @@ class Event extends BaseEvent + * @return Marking + */ +- public function getMarking() ++ public function getMarking(): Marking + { + return $this->marking; +@@ -50,5 +50,5 @@ class Event extends BaseEvent + * @return object + */ +- public function getSubject() ++ public function getSubject(): object + { + return $this->subject; +@@ -58,5 +58,5 @@ class Event extends BaseEvent + * @return Transition|null + */ +- public function getTransition() ++ public function getTransition(): ?Transition + { + return $this->transition; +@@ -71,5 +71,5 @@ class Event extends BaseEvent + * @return string + */ +- public function getWorkflowName() ++ public function getWorkflowName(): string + { + return $this->workflow->getName(); diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 801d9e270c20f..9dc6394adce41 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -72,7 +72,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$([[ ${{ matrix.os }} = macos* ]] && echo ',transient-on-macos')" >> $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) @@ -146,7 +146,7 @@ jobs: echo "::endgroup::" - name: Patch return types - if: "matrix.php == '8.1' && ! matrix.mode && matrix.os == 'ubuntu-20.04'" + if: "matrix.php == '8.1' && ! matrix.mode && matrix.os != 'macos-11'" run: | patch -sp1 < .github/expected-missing-return-types.diff git add . @@ -224,7 +224,7 @@ jobs: [[ ! $X ]] || (exit 1) - name: Run tests with SIGCHLD enabled PHP - if: "matrix.php == '8.0' && ! matrix.mode" + if: "matrix.php == '8.0' && ! matrix.mode && matrix.os != 'macos-11'" run: | mkdir build cd build diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 8b50502e98c7e..2333c06badd58 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -11,6 +11,7 @@ '@Symfony' => true, '@Symfony:risky' => true, 'protected_to_private' => false, + 'native_constant_invocation' => ['strict' => false], 'nullable_type_declaration_for_default_null_value' => ['use_nullable_type_declaration' => false], 'modernize_strpos' => true, ]) diff --git a/composer.json b/composer.json index 61a7251983f12..1750d55ee22ce 100644 --- a/composer.json +++ b/composer.json @@ -157,7 +157,7 @@ "egulias/email-validator": "~3.0.0", "masterminds/html5": "<2.6", "phpdocumentor/reflection-docblock": "<5.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/type-resolver": "<1.5.1", "ocramius/proxy-manager": "<2.1", "phpunit/phpunit": "<5.4.3" }, diff --git a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php index 82a4f36ce349c..d262c5e3591e4 100644 --- a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php +++ b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php @@ -56,7 +56,7 @@ public function dispatchEvent($eventName, EventArgs $eventArgs = null): void return; } - $eventArgs = $eventArgs ?? EventArgs::getEmptyInstance(); + $eventArgs ??= EventArgs::getEmptyInstance(); if (!isset($this->initialized[$eventName])) { $this->initializeListeners($eventName); diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index 367eb12e29162..4774019da3349 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -134,8 +134,6 @@ protected function setMappingDriverConfig(array $mappingConfig, string $mappingN * If this is a bundle controlled mapping all the missing information can be autodetected by this method. * * Returns false when autodetection failed, an array of the completed information otherwise. - * - * @return array|false */ protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, ContainerBuilder $container, string $bundleDir = null): array|false { diff --git a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php index f9bd6e0cbcf73..b4cc571bb56fc 100644 --- a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +++ b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php @@ -37,9 +37,7 @@ public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch */ public function startQuery($sql, array $params = null, array $types = null): void { - if (null !== $this->stopwatch) { - $this->stopwatch->start('doctrine', 'doctrine'); - } + $this->stopwatch?->start('doctrine', 'doctrine'); if (null !== $this->logger) { $this->log($sql, null === $params ? [] : $this->normalizeParams($params)); @@ -51,9 +49,7 @@ public function startQuery($sql, array $params = null, array $types = null): voi */ public function stopQuery(): void { - if (null !== $this->stopwatch) { - $this->stopwatch->stop('doctrine'); - } + $this->stopwatch?->stop('doctrine'); } /** diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php index 28d5fccc7459c..3e7922ec79276 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php @@ -30,9 +30,6 @@ public function __construct(ManagerRegistry $registry) public function initialize(object $object) { - $manager = $this->registry->getManagerForClass(\get_class($object)); - if (null !== $manager) { - $manager->initializeObject($object); - } + $this->registry->getManagerForClass(\get_class($object))?->initializeObject($object); } } diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php index d06a34a40f630..a4e695d37df1b 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php @@ -49,7 +49,7 @@ public function loadClassMetadata(ClassMetadata $metadata): bool $className = $metadata->getClassName(); try { $doctrineMetadata = $this->entityManager->getClassMetadata($className); - } catch (MappingException | OrmMappingException $exception) { + } catch (MappingException|OrmMappingException $exception) { return false; } diff --git a/src/Symfony/Bridge/Twig/AppVariable.php b/src/Symfony/Bridge/Twig/AppVariable.php index d21f6448a481b..b46646b5461ae 100644 --- a/src/Symfony/Bridge/Twig/AppVariable.php +++ b/src/Symfony/Bridge/Twig/AppVariable.php @@ -100,7 +100,7 @@ public function getSession(): ?Session } $request = $this->getRequest(); - return $request && $request->hasSession() ? $request->getSession() : null; + return $request?->hasSession() ? $request->getSession() : null; } /** diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index 6e73b9c3bb573..dfe66054bc496 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -230,9 +230,7 @@ private function renderException(SymfonyStyle $output, string $template, Error $ { $line = $exception->getTemplateLine(); - if ($githubReporter) { - $githubReporter->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line); - } + $githubReporter?->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line); if ($file) { $output->text(sprintf(' ERROR in %s (line %s)', $file, $line)); diff --git a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php index 360a386faedec..30dd92ff2ff3b 100644 --- a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php +++ b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php @@ -68,9 +68,6 @@ class UndefinedCallableHandler 'workflow' => 'enable "framework.workflows"', ]; - /** - * @return TwigFilter|false - */ public static function onUndefinedFilter(string $name): TwigFilter|false { if (!isset(self::FILTER_COMPONENTS[$name])) { @@ -80,9 +77,6 @@ public static function onUndefinedFilter(string $name): TwigFilter|false throw new SyntaxError(self::onUndefined($name, 'filter', self::FILTER_COMPONENTS[$name])); } - /** - * @return TwigFunction|false - */ public static function onUndefinedFunction(string $name): TwigFunction|false { if (!isset(self::FUNCTION_COMPONENTS[$name])) { diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 862a1ae089c7c..d3c5731888f6e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.1 +--- + + * Environment variable `SYMFONY_IDE` is read by default when `framework.ide` config is not set. + 6.0 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php index 70c42c1e05301..95f62d9202203 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php @@ -55,9 +55,7 @@ public function warmUp(string $cacheDir): array try { $this->dumpExtension($extension, $generator); } catch (\Exception $e) { - if ($this->logger) { - $this->logger->warning('Failed to generate ConfigBuilder for extension {extensionClass}.', ['exception' => $e, 'extensionClass' => \get_class($extension)]); - } + $this->logger?->warning('Failed to generate ConfigBuilder for extension {extensionClass}.', ['exception' => $e, 'extensionClass' => \get_class($extension)]); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php index 96f0f3762d393..a9d31e2217967 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php @@ -70,7 +70,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $secrets = $this->vault->list($reveal); - $localSecrets = null !== $this->localVault ? $this->localVault->list($reveal) : null; + $localSecrets = $this->localVault?->list($reveal); $rows = []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php index 3cb0a4ffcbb6d..eb96e65470ebf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @@ -36,7 +36,8 @@ class WorkflowDumpCommand extends Command { /** - * string is the service id + * string is the service id. + * * @var array */ private array $workflows = []; diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index 1ae52cb1ee1a1..86c365c258fb6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -70,8 +70,6 @@ public function setContainer(ContainerInterface $container): ?ContainerInterface /** * Gets a container parameter by its name. - * - * @return array|bool|float|int|string|null */ protected function getParameter(string $name): array|bool|float|int|string|null { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 198a434c98603..5b35c99ec6741 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -80,7 +80,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->info("Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. Note: When using the HttpCache, you need to call the method in your front controller instead") ->defaultTrue() ->end() - ->scalarNode('ide')->defaultNull()->end() + ->scalarNode('ide')->defaultValue('%env(default::SYMFONY_IDE)%')->end() ->booleanNode('test')->end() ->scalarNode('default_locale')->defaultValue('en')->end() ->booleanNode('set_locale_from_accept_language') @@ -2038,6 +2038,9 @@ private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIf ->scalarNode('time_based_uuid_node') ->cannotBeEmpty() ->end() + ->arrayNode('argument_value_resolver') + ->canBeEnabled() + ->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 2a74d17458882..9451cd74efde1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -190,6 +190,8 @@ use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand; use Symfony\Component\Translation\PseudoLocalizationTranslator; use Symfony\Component\Translation\Translator; +use Symfony\Component\Uid\AbstractUid; +use Symfony\Component\Uid\ArgumentResolver\UidValueResolver; use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Uid\UuidV4; use Symfony\Component\Validator\ConstraintValidatorInterface; @@ -2545,6 +2547,18 @@ private function registerUidConfiguration(array $config, ContainerBuilder $conta $container->getDefinition('name_based_uuid.factory') ->setArguments([$config['name_based_uuid_namespace']]); } + + if (!$config['argument_value_resolver']['enabled']) { + $container->removeDefinition('argument_resolver.uid'); + + return; + } elseif (!class_exists(UidValueResolver::class)) { + throw new LogicException('Uid argument value resolver cannot be enabled as the installed Uid component version is too low. Try running "composer require symfony/uid:^6.1".'); + } + + $container->setParameter('uid.base58', AbstractUid::BASE58); + $container->setParameter('uid.base32', AbstractUid::BASE32); + $container->setParameter('uid.rfc4122', AbstractUid::RFC4122); } private function resolveTrustedHeaders(array $headers): int diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index a85eb3d017c1e..6795e3c22aa0e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -62,8 +62,6 @@ public function getKernel(): KernelInterface /** * Gets the profile associated with the current Response. - * - * @return HttpProfile|false|null */ public function getProfile(): HttpProfile|false|null { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_debug.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_debug.php index 3fb6ce0a42d49..cdb205750f05d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_debug.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_debug.php @@ -12,7 +12,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\Mailer\DataCollector\MessageDataCollector; -use Symfony\Component\Mailer\EventListener\MessageLoggerListener; return static function (ContainerConfigurator $container) { $container->services() diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index d48e8ca1520f7..1a9b7f611bf04 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -735,6 +735,9 @@ + + + @@ -765,6 +768,10 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/uid.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/uid.php index 840fb97b5f5f5..f6a40a656feeb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/uid.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/uid.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Uid\ArgumentResolver\UidValueResolver; use Symfony\Component\Uid\Factory\NameBasedUuidFactory; use Symfony\Component\Uid\Factory\RandomBasedUuidFactory; use Symfony\Component\Uid\Factory\TimeBasedUuidFactory; @@ -37,5 +38,8 @@ ->set('time_based_uuid.factory', TimeBasedUuidFactory::class) ->factory([service('uuid.factory'), 'timeBased']) ->alias(TimeBasedUuidFactory::class, 'time_based_uuid.factory') + + ->set('argument_resolver.uid', UidValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 150]) // Higher than RequestAttributeValueResolver ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php index ba02c761d73dc..d68ae4c8b3ba5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -84,6 +84,8 @@ protected static function bootKernel(array $options = []): KernelInterface * used by other services. * * Using this method is the best way to get a container from your test code. + * + * @return TestContainer */ protected static function getContainer(): ContainerInterface { diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php index 4ce5772d91e63..75a87870691d2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php @@ -63,8 +63,6 @@ public function getParameterBag(): ParameterBagInterface /** * {@inheritdoc} - * - * @return array|bool|float|int|string|null */ public function getParameter(string $name): array|bool|float|int|string|null { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index abf4503fad168..7c8f71011fc3f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -368,7 +368,7 @@ protected static function getBundleDefaultConfig() { return [ 'http_method_override' => true, - 'ide' => null, + 'ide' => '%env(default::SYMFONY_IDE)%', 'default_locale' => 'en', 'enabled_locales' => [], 'set_locale_from_accept_language' => false, @@ -580,6 +580,9 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'default_uuid_version' => 6, 'name_based_uuid_version' => 5, 'time_based_uuid_version' => 6, + 'argument_value_resolver' => [ + 'enabled' => false, + ], ], 'exceptions' => [], ]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index e0a90423700c2..4b34e561f10b7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -14,7 +14,6 @@ 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; 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..a4238e3fa53c3 --- /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' => Ulid::BASE58])] + public function specificFormatInAttribute(Ulid $id): Response + { + return new Response($id); + } + + #[Route(path: '/3/uuid-v1/{id<%uid.base32%>}')] + public function specificFormatInPath(UuidV1 $id): Response + { + return new Response($id); + } + + #[Route(path: '/4/uuid-v1/{postId}/custom-uid/{commentId}')] + public function manyUids(UuidV1 $postId, TestCommentIdentifier $commentId): Response + { + return new Response($postId."\n".$commentId); + } +} + +class TestCommentIdentifier extends Ulid +{ +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php index fb70137ff7b22..9120032e11028 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php @@ -27,9 +27,7 @@ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('test'); - if ($this->customConfig) { - $this->customConfig->addConfiguration($treeBuilder->getRootNode()); - } + $this->customConfig?->addConfiguration($treeBuilder->getRootNode()); return $treeBuilder; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml index 8d41ce3267131..bfd37826b268b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml @@ -60,3 +60,7 @@ array_controller: send_email: path: /send_email defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\EmailController::indexAction } + +uid: + resource: "../../Controller/UidController.php" + type: "annotation" diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/UidTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/UidTest.php new file mode 100644 index 0000000000000..a2ff1f4614000 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/UidTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\UidController; +use Symfony\Component\Uid\ArgumentResolver\UidValueResolver; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\UuidV1; +use Symfony\Component\Uid\UuidV4; +use Symfony\Component\Uid\UuidV6; + +class UidTest extends AbstractWebTestCase +{ + /** + * @see UidController + */ + public function testArgumentValueResolverConfigAllDefaults() + { + if (!class_exists(UidValueResolver::class)) { + $this->markTestSkipped('Needs symfony/uid >= 6.1'); + } + + $client = $this->createClient(['test_case' => 'Uid', 'root_config' => 'config.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.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/config.yml new file mode 100644 index 0000000000000..fad7b8d7e56c4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Uid/config.yml @@ -0,0 +1,6 @@ +imports: + - { resource: "../config/default.yml" } + +framework: + uid: + argument_value_resolver: ~ 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/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 1d73dd97e57c0..eaaec6be0e8b6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -58,6 +58,7 @@ "symfony/string": "^5.4|^6.0", "symfony/translation": "^5.4|^6.0", "symfony/twig-bundle": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", "symfony/validator": "^5.4|^6.0", "symfony/workflow": "^5.4|^6.0", "symfony/yaml": "^5.4|^6.0", diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index 27c6e10bcfde0..394006f55b66b 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -110,9 +110,7 @@ public function collect(Request $request, Response $response, \Throwable $except $logoutUrl = null; try { - if (null !== $this->logoutUrlGenerator) { - $logoutUrl = $this->logoutUrlGenerator->getLogoutPath(); - } + $logoutUrl = $this->logoutUrlGenerator?->getLogoutPath(); } catch (\Exception $e) { // fail silently when the logout URL cannot be generated } @@ -187,7 +185,6 @@ public function collect(Request $request, Response $response, \Throwable $except 'authenticators' => $firewallConfig->getAuthenticators(), ]; - // generate exit impersonation path from current request if ($this->data['impersonated'] && null !== $switchUserConfig = $firewallConfig->getSwitchUser()) { $exitPath = $request->getRequestUri(); diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php index 2ae1f4af9bb34..357d2414d8bf2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php @@ -45,7 +45,7 @@ public function getInfo(): array return [ 'response' => $this->response, 'time' => $this->time, - 'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener), + 'stub' => $this->stub ??= ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener), ]; } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index d9ec8167f20fe..d6973f2b668d4 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -65,7 +65,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->booleanNode('hide_user_not_found')->defaultTrue()->end() ->booleanNode('erase_credentials')->defaultTrue()->end() - ->booleanNode('enable_authenticator_manager')->defaultFalse()->info('Enables the new Symfony Security system based on Authenticators, all used authenticators must support this before enabling this.')->end() + ->booleanNode('enable_authenticator_manager')->defaultTrue()->end() ->arrayNode('access_decision_manager') ->addDefaultsIfNotSet() ->children() diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index 4e76f9bcc1404..6b73f21ec2b41 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -71,9 +71,7 @@ public function panelAction(Request $request, string $token): Response { $this->denyAccessIfProfilerDisabled(); - if (null !== $this->cspHandler) { - $this->cspHandler->disableCsp(); - } + $this->cspHandler?->disableCsp(); $panel = $request->query->get('panel'); $page = $request->query->get('page', 'home'); @@ -172,9 +170,7 @@ public function searchBarAction(Request $request): Response { $this->denyAccessIfProfilerDisabled(); - if (null !== $this->cspHandler) { - $this->cspHandler->disableCsp(); - } + $this->cspHandler?->disableCsp(); if (!$request->hasSession()) { $ip = @@ -224,9 +220,7 @@ public function searchResultsAction(Request $request, string $token): Response { $this->denyAccessIfProfilerDisabled(); - if (null !== $this->cspHandler) { - $this->cspHandler->disableCsp(); - } + $this->cspHandler?->disableCsp(); $profile = $this->profiler->loadProfile($token); @@ -312,9 +306,7 @@ public function phpinfoAction(): Response { $this->denyAccessIfProfilerDisabled(); - if (null !== $this->cspHandler) { - $this->cspHandler->disableCsp(); - } + $this->cspHandler?->disableCsp(); ob_start(); phpinfo(); @@ -336,9 +328,7 @@ public function xdebugAction(): Response throw new NotFoundHttpException('Xdebug must be installed in version 3.'); } - if (null !== $this->cspHandler) { - $this->cspHandler->disableCsp(); - } + $this->cspHandler?->disableCsp(); ob_start(); xdebug_info(); @@ -358,9 +348,7 @@ public function openAction(Request $request): Response throw new NotFoundHttpException('The base dir should be set.'); } - if ($this->profiler) { - $this->profiler->disable(); - } + $this->profiler?->disable(); $file = $request->query->get('file'); $line = $request->query->get('line'); diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index f4bfec100e3a6..5152223669c8d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -95,7 +95,7 @@ public function onKernelResponse(ResponseEvent $event) $nonces = []; if ($this->cspHandler) { - if ($this->dumpDataCollector && $this->dumpDataCollector->getDumpsCount() > 0) { + if ($this->dumpDataCollector?->getDumpsCount() > 0) { $this->cspHandler->disableCsp(); } diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 19f282e23391b..19be14f1d01a4 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -74,7 +74,7 @@ static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) $key = (string) $key; if (null === $item->expiry) { $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; - } elseif (0 === $item->expiry) { + } elseif (!$item->expiry) { $ttl = 0; } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { $expiredIds[] = $getId($key); diff --git a/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php index 476cec9d15a49..f66c8370d5b91 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractTagAwareAdapter.php @@ -79,7 +79,7 @@ static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime) $key = (string) $key; if (null === $item->expiry) { $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; - } elseif (0 === $item->expiry) { + } elseif (!$item->expiry) { $ttl = 0; } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { $expiredIds[] = $getId($key); diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php index 3a37297cddba1..d6eb152f08fcb 100644 --- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php @@ -182,14 +182,14 @@ public function save(CacheItemInterface $item): bool $now = microtime(true); - if (0 === $expiry) { - $expiry = \PHP_INT_MAX; - } - - if (null !== $expiry && $expiry <= $now) { - $this->deleteItem($key); + if (null !== $expiry) { + if (!$expiry) { + $expiry = \PHP_INT_MAX; + } elseif ($expiry <= $now) { + $this->deleteItem($key); - return true; + return true; + } } if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) { return false; diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php index 925e9f2f6b5bc..64ae546440aad 100644 --- a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php @@ -66,10 +66,10 @@ public function __construct(array $adapters, int $defaultLifetime = 0) $this->adapterCount = \count($this->adapters); $this->defaultLifetime = $defaultLifetime; - self::$syncItem ?? self::$syncItem = \Closure::bind( + self::$syncItem ??= \Closure::bind( static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) { $sourceItem->isTaggable = false; - $sourceMetadata = $sourceMetadata ?? $sourceItem->metadata; + $sourceMetadata ??= $sourceItem->metadata; unset($sourceMetadata[CacheItem::METADATA_TAGS]); $item->value = $sourceItem->value; @@ -108,7 +108,7 @@ public function get(string $key, callable $callback, float $beta = null, array & $value = $this->doGet($adapter, $key, $callback, $beta, $metadata); } if (null !== $item) { - (self::$syncItem)($lastItem = $lastItem ?? $item, $item, $this->defaultLifetime, $metadata); + (self::$syncItem)($lastItem ??= $item, $item, $this->defaultLifetime, $metadata); } return $value; diff --git a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php index 13e45435f0f75..7ec96f5d88eba 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php @@ -135,7 +135,7 @@ protected function doFetch(array $ids): iterable foreach ($missingIds as $k => $id) { try { - $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); + $file = $this->files[$id] ??= $this->getFile($id); if (isset(self::$valuesCache[$file])) { [$expiresAt, $this->values[$id]] = self::$valuesCache[$file]; @@ -176,7 +176,7 @@ protected function doHave(string $id): bool set_error_handler($this->includeHandler); try { - $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); + $file = $this->files[$id] ??= $this->getFile($id); $getExpiry = true; if (isset(self::$valuesCache[$file])) { diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index c90b80e4cbd51..23434cd4714c9 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -92,7 +92,7 @@ static function (CacheItemInterface $innerItem, array $item) { $item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]]; } $innerItem->set($item["\0*\0value"]); - $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6F', 0 === $item["\0*\0expiry"] ? \PHP_INT_MAX : $item["\0*\0expiry"])) : null); + $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6F', $item["\0*\0expiry"])) : null); }, null, CacheItem::class diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php index 1ec5a8cc30846..cb1c638c5c0db 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php @@ -55,7 +55,7 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter private const DEFAULT_CACHE_TTL = 8640000; /** - * detected eviction policy used on Redis server + * detected eviction policy used on Redis server. */ private string $redisEvictionPolicy; diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index a36551d308de7..0176383ef063c 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -48,7 +48,7 @@ public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsP $this->pool = $itemsPool; $this->tags = $tagsPool ?? $itemsPool; $this->knownTagVersionsTtl = $knownTagVersionsTtl; - self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + self::$createCacheItem ??= \Closure::bind( static function ($key, $value, CacheItem $protoItem) { $item = new CacheItem(); $item->key = $key; @@ -61,7 +61,7 @@ static function ($key, $value, CacheItem $protoItem) { null, CacheItem::class ); - self::$setCacheItemTags ?? self::$setCacheItemTags = \Closure::bind( + self::$setCacheItemTags ??= \Closure::bind( static function (CacheItem $item, $key, array &$itemTags) { $item->isTaggable = true; if (!$item->isHit) { @@ -82,7 +82,7 @@ static function (CacheItem $item, $key, array &$itemTags) { null, CacheItem::class ); - self::$getTagsByKey ?? self::$getTagsByKey = \Closure::bind( + self::$getTagsByKey ??= \Closure::bind( static function ($deferred) { $tagsByKey = []; foreach ($deferred as $key => $item) { @@ -95,7 +95,7 @@ static function ($deferred) { null, CacheItem::class ); - self::$saveTags ?? self::$saveTags = \Closure::bind( + self::$saveTags ??= \Closure::bind( static function (AdapterInterface $tagsAdapter, array $tags) { ksort($tags); @@ -394,7 +394,7 @@ private function getTagVersions(array $tagsByKey): array $newVersion = null; foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) { if (!$version->isHit()) { - $newTags[$tag] = $version->set($newVersion ?? $newVersion = random_int(\PHP_INT_MIN, \PHP_INT_MAX)); + $newTags[$tag] = $version->set($newVersion ??= random_int(\PHP_INT_MIN, \PHP_INT_MAX)); } $tagVersions[$tag = $tags[$tag]] = $version->get(); $this->knownTagVersions[$tag] = [$now, $tagVersions[$tag]]; diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index a1dc4617e5b13..1f54db7b8bd80 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -10,7 +10,6 @@ CHANGELOG 5.4 --- - * Make `LockRegistry` use semaphores when possible * Deprecate `DoctrineProvider` and `DoctrineAdapter` because these classes have been added to the `doctrine/cache` package * Add `DoctrineDbalAdapter` identical to `PdoAdapter` for `Doctrine\DBAL\Connection` or DBAL URL * Deprecate usage of `PdoAdapter` with `Doctrine\DBAL\Connection` or DBAL URL diff --git a/src/Symfony/Component/Cache/LockRegistry.php b/src/Symfony/Component/Cache/LockRegistry.php index 5803bccc7688d..da67036dc4666 100644 --- a/src/Symfony/Component/Cache/LockRegistry.php +++ b/src/Symfony/Component/Cache/LockRegistry.php @@ -27,7 +27,7 @@ final class LockRegistry { private static $openedFiles = []; - private static $lockedKeys; + private static $lockedFiles; /** * The number of items in this list controls the max number of concurrent processes. @@ -76,25 +76,21 @@ public static function setFiles(array $files): array fclose($file); } } - self::$openedFiles = self::$lockedKeys = []; + self::$openedFiles = self::$lockedFiles = []; return $previousFiles; } public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null) { - if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedKeys) { + if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) { // disable locking on Windows by default - self::$files = self::$lockedKeys = []; + self::$files = self::$lockedFiles = []; } - $key = unpack('i', md5($item->getKey(), true))[1]; + $key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1; - if (!\function_exists('sem_get')) { - $key = self::$files ? abs($key) % \count(self::$files) : null; - } - - if (null === $key || (self::$lockedKeys[$key] ?? false) || !$lock = self::open($key)) { + if ($key < 0 || (self::$lockedFiles[$key] ?? false) || !$lock = self::open($key)) { return $callback($item, $save); } @@ -102,15 +98,11 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s try { $locked = false; // race to get the lock in non-blocking mode - if ($wouldBlock = \function_exists('sem_get')) { - $locked = @sem_acquire($lock, true); - } else { - $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock); - } + $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock); if ($locked || !$wouldBlock) { - $logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]); - self::$lockedKeys[$key] = true; + $logger?->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]); + self::$lockedFiles[$key] = true; $value = $callback($item, $save); @@ -125,25 +117,12 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s return $value; } - // if we failed the race, retry locking in blocking mode to wait for the winner - $logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]); - - if (\function_exists('sem_get')) { - $lock = sem_get($key); - @sem_acquire($lock); - } else { - flock($lock, \LOCK_SH); - } + $logger?->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]); + flock($lock, \LOCK_SH); } finally { - if ($locked) { - if (\function_exists('sem_get')) { - sem_remove($lock); - } else { - flock($lock, \LOCK_UN); - } - } - unset(self::$lockedKeys[$key]); + flock($lock, \LOCK_UN); + unset(self::$lockedFiles[$key]); } static $signalingException, $signalingCallback; $signalingException = $signalingException ?? unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}"); @@ -151,7 +130,7 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s try { $value = $pool->get($item->getKey(), $signalingCallback, 0); - $logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]); + $logger?->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]); $save = false; return $value; @@ -159,7 +138,7 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s if ($signalingException !== $e) { throw $e; } - $logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]); + $logger?->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]); } } @@ -168,10 +147,6 @@ public static function compute(callable $callback, ItemInterface $item, bool &$s private static function open(int $key) { - if (\function_exists('sem_get')) { - return sem_get($key); - } - if (null !== $h = self::$openedFiles[$key] ?? null) { return $h; } diff --git a/src/Symfony/Component/Cache/Marshaller/DefaultMarshaller.php b/src/Symfony/Component/Cache/Marshaller/DefaultMarshaller.php index 7459a9db47029..5c34fb2ea59d3 100644 --- a/src/Symfony/Component/Cache/Marshaller/DefaultMarshaller.php +++ b/src/Symfony/Component/Cache/Marshaller/DefaultMarshaller.php @@ -71,7 +71,7 @@ public function unmarshall(string $value): mixed return null; } static $igbinaryNull; - if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') ? igbinary_serialize(null) : false)) { + if ($value === $igbinaryNull ??= \extension_loaded('igbinary') ? igbinary_serialize(null) : false) { return null; } $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); diff --git a/src/Symfony/Component/Cache/Messenger/EarlyExpirationDispatcher.php b/src/Symfony/Component/Cache/Messenger/EarlyExpirationDispatcher.php index 25b5023b32fd3..5aeacf1d598ef 100644 --- a/src/Symfony/Component/Cache/Messenger/EarlyExpirationDispatcher.php +++ b/src/Symfony/Component/Cache/Messenger/EarlyExpirationDispatcher.php @@ -38,7 +38,7 @@ public function __invoke(callable $callback, CacheItem $item, bool &$save, Adapt { if (!$item->isHit() || null === $message = EarlyExpirationMessage::create($this->reverseContainer, $callback, $item, $pool)) { // The item is stale or the callback cannot be reversed: we must compute the value now - $logger && $logger->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]); + $logger?->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]); return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $setMetadata, $logger) : $callback($item, $save); } diff --git a/src/Symfony/Component/Cache/Messenger/EarlyExpirationHandler.php b/src/Symfony/Component/Cache/Messenger/EarlyExpirationHandler.php index 043b4a177c6fb..9d2aaecc76c83 100644 --- a/src/Symfony/Component/Cache/Messenger/EarlyExpirationHandler.php +++ b/src/Symfony/Component/Cache/Messenger/EarlyExpirationHandler.php @@ -59,7 +59,7 @@ public function __invoke(EarlyExpirationMessage $message) static $setMetadata; - $setMetadata ?? $setMetadata = \Closure::bind( + $setMetadata ??= \Closure::bind( function (CacheItem $item, float $startTime) { if ($item->expiry > $endTime = microtime(true)) { $item->newMetadata[CacheItem::METADATA_EXPIRY] = $item->expiry; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php index f7c5b83124970..eee85bff6ee53 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php @@ -128,7 +128,7 @@ public function testGetMetadata() $metadata = $item->getMetadata(); $this->assertArrayHasKey(CacheItem::METADATA_CTIME, $metadata); - $this->assertEqualsWithDelta(999, $metadata[CacheItem::METADATA_CTIME], 10); + $this->assertEqualsWithDelta(999, $metadata[CacheItem::METADATA_CTIME], 150); $this->assertArrayHasKey(CacheItem::METADATA_EXPIRY, $metadata); $this->assertEqualsWithDelta(9 + time(), $metadata[CacheItem::METADATA_EXPIRY], 1); } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterAndRedisAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterAndRedisAdapterTest.php new file mode 100644 index 0000000000000..46516e0095e6e --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterAndRedisAdapterTest.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\Component\Cache\Tests\Adapter; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Adapter\ProxyAdapter; +use Symfony\Component\Cache\Adapter\RedisAdapter; +use Symfony\Component\Cache\CacheItem; + +/** + * @group integration + */ +class ProxyAdapterAndRedisAdapterTest extends AbstractRedisAdapterTest +{ + protected $skippedTests = [ + 'testPrune' => 'RedisAdapter does not implement PruneableInterface.', + ]; + + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST')); + } + + public function createCachePool($defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface + { + return new ProxyAdapter(new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), 100), 'ProxyNS', $defaultLifetime); + } + + public function testSaveItemPermanently() + { + $setCacheItemExpiry = \Closure::bind( + static function (CacheItem $item, $expiry) { + $item->expiry = $expiry; + + return $item; + }, + null, + CacheItem::class + ); + + $cache = $this->createCachePool(1); + $value = rand(); + $item = $cache->getItem('foo'); + $setCacheItemExpiry($item, 0); + $cache->save($item->set($value)); + $item = $cache->getItem('bar'); + $setCacheItemExpiry($item, 0.0); + $cache->save($item->set($value)); + $item = $cache->getItem('baz'); + $cache->save($item->set($value)); + + $this->assertSame($value, $this->cache->getItem('foo')->get()); + $this->assertSame($value, $this->cache->getItem('bar')->get()); + $this->assertSame($value, $this->cache->getItem('baz')->get()); + + sleep(1); + $this->assertSame($value, $this->cache->getItem('foo')->get()); + $this->assertSame($value, $this->cache->getItem('bar')->get()); + $this->assertFalse($this->cache->getItem('baz')->isHit()); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php index 54ce7822e06af..81e943c144f56 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php @@ -14,12 +14,10 @@ use PHPUnit\Framework\MockObject\MockObject; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; -use Psr\Log\LoggerInterface; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\TagAwareAdapter; -use Symfony\Component\Cache\LockRegistry; use Symfony\Component\Cache\Tests\Fixtures\PrunableAdapter; use Symfony\Component\Filesystem\Filesystem; @@ -181,24 +179,6 @@ public function testGetItemReturnsCacheMissWhenPoolDoesNotHaveItemAndOnlyHasTags $this->assertFalse($item->isHit()); } - public function testLog() - { - $lockFiles = LockRegistry::setFiles([__FILE__]); - - $logger = $this->createMock(LoggerInterface::class); - $logger - ->expects($this->atLeastOnce()) - ->method($this->anything()); - - $cache = new TagAwareAdapter(new ArrayAdapter()); - $cache->setLogger($logger); - - // Computing will produce at least one log - $cache->get('foo', static function (): string { return 'ccc'; }); - - LockRegistry::setFiles($lockFiles); - } - /** * @return MockObject&PruneableCacheInterface */ diff --git a/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php b/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php index 0149b7674c50e..64e35a47a174f 100644 --- a/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php +++ b/src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php @@ -26,12 +26,12 @@ trait AbstractAdapterTrait use LoggerAwareTrait; /** - * needs to be set by class, signature is function(string , mixed , bool ) + * needs to be set by class, signature is function(string , mixed , bool ). */ private static \Closure $createCacheItem; /** - * needs to be set by class, signature is function(array , string , array <&expiredIds>) + * needs to be set by class, signature is function(array , string , array <&expiredIds>). */ private static \Closure $mergeByLifetime; diff --git a/src/Symfony/Component/Cache/Traits/ContractsTrait.php b/src/Symfony/Component/Cache/Traits/ContractsTrait.php index 6361f95161a29..f63b4957ac9ac 100644 --- a/src/Symfony/Component/Cache/Traits/ContractsTrait.php +++ b/src/Symfony/Component/Cache/Traits/ContractsTrait.php @@ -41,12 +41,20 @@ trait ContractsTrait */ public function setCallbackWrapper(?callable $callbackWrapper): callable { - $previousWrapper = $this->callbackWrapper ??= \Closure::fromCallable([LockRegistry::class, 'compute']); + if (!isset($this->callbackWrapper)) { + $this->callbackWrapper = \Closure::fromCallable([LockRegistry::class, 'compute']); + + if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + $this->setCallbackWrapper(null); + } + } + if (null !== $callbackWrapper && !$callbackWrapper instanceof \Closure) { $callbackWrapper = \Closure::fromCallable($callbackWrapper); } - $this->callbackWrapper = $callbackWrapper ?? function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) { + $previousWrapper = $this->callbackWrapper; + $this->callbackWrapper = $callbackWrapper ?? static function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) { return $callback($item, $save); }; @@ -55,13 +63,13 @@ public function setCallbackWrapper(?callable $callbackWrapper): callable private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null) { - if (0 > $beta = $beta ?? 1.0) { + if (0 > $beta ??= 1.0) { throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)); } static $setMetadata; - $setMetadata ?? $setMetadata = \Closure::bind( + $setMetadata ??= \Closure::bind( static function (CacheItem $item, float $startTime, ?array &$metadata) { if ($item->expiry > $endTime = microtime(true)) { $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry; @@ -88,6 +96,10 @@ static function (CacheItem $item, float $startTime, ?array &$metadata) { $this->computing[$key] = $key; $startTime = microtime(true); + if (!isset($this->callbackWrapper)) { + $this->setCallbackWrapper($this->setCallbackWrapper(null)); + } + try { $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) { $setMetadata($item, $startTime, $metadata); diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index fc833beb7f23a..9a48944bafd4a 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -441,7 +441,7 @@ protected function doDelete(array $ids): bool if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { static $del; - $del = $del ?? (class_exists(UNLINK::class) ? 'unlink' : 'del'); + $del ??= (class_exists(UNLINK::class) ? 'unlink' : 'del'); $this->pipeline(function () use ($ids, $del) { foreach ($ids as $id) { @@ -498,7 +498,7 @@ protected function doSave(array $values, int $lifetime): array|bool private function pipeline(\Closure $generator, object $redis = null): \Generator { $ids = []; - $redis = $redis ?? $this->redis; + $redis ??= $this->redis; if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster)) { // phpredis & predis don't support pipelining with RedisCluster diff --git a/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php b/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php index 71d156dfc47a1..22f8280d6018a 100644 --- a/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php @@ -204,8 +204,6 @@ public function thenUnset(): static /** * Returns the related node. * - * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition - * * @throws \RuntimeException */ public function end(): NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition diff --git a/src/Symfony/Component/Config/Definition/Builder/MergeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/MergeBuilder.php index 1b55b185bd970..f8980a6e041c9 100644 --- a/src/Symfony/Component/Config/Definition/Builder/MergeBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/MergeBuilder.php @@ -53,8 +53,6 @@ public function denyOverwrite(bool $deny = true): static /** * Returns the related node. - * - * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition */ public function end(): NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition { diff --git a/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php index 4a106d1d289d2..e469e69044c3f 100644 --- a/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php @@ -25,7 +25,7 @@ class TreeBuilder implements NodeParentInterface public function __construct(string $name, string $type = 'array', NodeBuilder $builder = null) { - $builder = $builder ?? new NodeBuilder(); + $builder ??= new NodeBuilder(); $this->root = $builder->node($name, $type)->setParent($this); } diff --git a/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php b/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php index e85558cac8d96..c988c0ab30bb7 100644 --- a/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php +++ b/src/Symfony/Component/Config/Tests/Builder/GeneratedConfigTest.php @@ -148,7 +148,7 @@ public function testSetExtraKeyMethodIsNotGeneratedWhenAllowExtraKeysIsFalse() */ private function generateConfigBuilder(string $configurationClass, string $outputDir = null) { - $outputDir ?? $outputDir = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('sf_config_builder', true); + $outputDir ??= sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('sf_config_builder', true); if (!str_contains($outputDir, __DIR__)) { $this->tempDir[] = $outputDir; } diff --git a/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php b/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php index 184917b9eddbb..ce6772f06556d 100644 --- a/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php +++ b/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php @@ -169,6 +169,8 @@ public function getDataForPhpize(): array [1, '1'], [-1, '-1'], [0777, '0777'], + [-511, '-0777'], + ['0877', '0877'], [255, '0xFF'], [100.0, '1e2'], [-120.0, '-1.2E2'], diff --git a/src/Symfony/Component/Config/Util/XmlUtils.php b/src/Symfony/Component/Config/Util/XmlUtils.php index 1f81a2cd2a792..7d1644b701d0a 100644 --- a/src/Symfony/Component/Config/Util/XmlUtils.php +++ b/src/Symfony/Component/Config/Util/XmlUtils.php @@ -216,15 +216,11 @@ public static function phpize(string|\Stringable $value): mixed case 'null' === $lowercaseValue: return null; case ctype_digit($value): - $raw = $value; - $cast = (int) $value; - - return '0' == $value[0] ? octdec($value) : (($raw === (string) $cast) ? $cast : $raw); case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)): $raw = $value; $cast = (int) $value; - return '0' == $value[1] ? octdec($value) : (($raw === (string) $cast) ? $cast : $raw); + return self::isOctal($value) ? \intval($value, 8) : (($raw === (string) $cast) ? $cast : $raw); case 'true' === $lowercaseValue: return true; case 'false' === $lowercaseValue: @@ -261,4 +257,13 @@ protected static function getXmlErrors(bool $internalErrors) return $errors; } + + private static function isOctal(string $str): bool + { + if ('-' === $str[0]) { + $str = substr($str, 1); + } + + return $str === '0'.decoct(\intval($str, 8)); + } } diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index b582435f7d102..0ed5649b8ac55 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -558,7 +558,7 @@ public function has(string $name): bool { $this->init(); - return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name))); + return isset($this->commands[$name]) || ($this->commandLoader?->has($name) && $this->add($this->commandLoader->get($name))); } /** diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index d6354b4ab119c..0bd3426c07eb3 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -421,9 +421,7 @@ public function getNativeDefinition(): InputDefinition public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null): static { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); - if (null !== $this->fullDefinition) { - $this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default)); - } + $this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default)); return $this; } @@ -442,9 +440,7 @@ public function addArgument(string $name, int $mode = null, string $description public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null): static { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); - if (null !== $this->fullDefinition) { - $this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); - } + $this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); return $this; } @@ -560,7 +556,7 @@ public function getHelp(): string public function getProcessedHelp(): string { $name = $this->name; - $isSingleCommand = $this->application && $this->application->isSingleCommand(); + $isSingleCommand = $this->application?->isSingleCommand(); $placeholders = [ '%command.name%', diff --git a/src/Symfony/Component/Console/Command/CompleteCommand.php b/src/Symfony/Component/Console/Command/CompleteCommand.php index 97357d6737ed3..4fb3398eb9586 100644 --- a/src/Symfony/Component/Console/Command/CompleteCommand.php +++ b/src/Symfony/Component/Console/Command/CompleteCommand.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Completion\Output\BashCompletionOutput; use Symfony\Component\Console\Completion\Output\CompletionOutputInterface; +use Symfony\Component\Console\Completion\Output\FishCompletionOutput; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Input\InputInterface; @@ -41,7 +42,10 @@ final class CompleteCommand extends Command public function __construct(array $completionOutputs = []) { // must be set before the parent constructor, as the property value is used in configure() - $this->completionOutputs = $completionOutputs + ['bash' => BashCompletionOutput::class]; + $this->completionOutputs = $completionOutputs + [ + 'bash' => BashCompletionOutput::class, + 'fish' => FishCompletionOutput::class, + ]; parent::__construct(); } diff --git a/src/Symfony/Component/Console/Completion/CompletionInput.php b/src/Symfony/Component/Console/Completion/CompletionInput.php index eda95bef55468..b0f00088e7150 100644 --- a/src/Symfony/Component/Console/Completion/CompletionInput.php +++ b/src/Symfony/Component/Console/Completion/CompletionInput.php @@ -84,7 +84,7 @@ public function bind(InputDefinition $definition): void return; } - if (null !== $option && $option->acceptValue()) { + if ($option?->acceptValue()) { $this->completionType = self::TYPE_OPTION_VALUE; $this->completionName = $option->getName(); $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : ''); @@ -97,7 +97,7 @@ public function bind(InputDefinition $definition): void if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) { // check if previous option accepted a value $previousOption = $this->getOptionFromToken($previousToken); - if (null !== $previousOption && $previousOption->acceptValue()) { + if ($previousOption?->acceptValue()) { $this->completionType = self::TYPE_OPTION_VALUE; $this->completionName = $previousOption->getName(); $this->completionValue = $relevantToken; diff --git a/src/Symfony/Component/Console/Completion/Output/FishCompletionOutput.php b/src/Symfony/Component/Console/Completion/Output/FishCompletionOutput.php new file mode 100644 index 0000000000000..9b02f09aa8250 --- /dev/null +++ b/src/Symfony/Component/Console/Completion/Output/FishCompletionOutput.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Completion\Output; + +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Guillaume Aveline + */ +class FishCompletionOutput implements CompletionOutputInterface +{ + public function write(CompletionSuggestions $suggestions, OutputInterface $output): void + { + $values = $suggestions->getValueSuggestions(); + foreach ($suggestions->getOptionSuggestions() as $option) { + $values[] = '--'.$option->getName(); + } + $output->write(implode("\n", $values)); + } +} diff --git a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php index efaf39a2c9a20..4334556663078 100644 --- a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php +++ b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php @@ -87,7 +87,7 @@ public function process(ContainerBuilder $container) $lazyCommandMap[$tag['command']] = $id; } - $description = $description ?? $tag['description'] ?? null; + $description ??= $tag['description'] ?? null; } $definition->addMethodCall('setName', [$commandName]); diff --git a/src/Symfony/Component/Console/EventListener/ErrorListener.php b/src/Symfony/Component/Console/EventListener/ErrorListener.php index bcd9183311de7..773a13a1c0df2 100644 --- a/src/Symfony/Component/Console/EventListener/ErrorListener.php +++ b/src/Symfony/Component/Console/EventListener/ErrorListener.php @@ -79,7 +79,7 @@ public static function getSubscribedEvents(): array private static function getInputString(ConsoleEvent $event): ?string { - $commandName = $event->getCommand() ? $event->getCommand()->getName() : null; + $commandName = $event->getCommand()?->getName(); $input = $event->getInput(); if ($input instanceof \Stringable) { diff --git a/src/Symfony/Component/Console/Formatter/NullOutputFormatter.php b/src/Symfony/Component/Console/Formatter/NullOutputFormatter.php index d14db326a050d..7950fad1ede30 100644 --- a/src/Symfony/Component/Console/Formatter/NullOutputFormatter.php +++ b/src/Symfony/Component/Console/Formatter/NullOutputFormatter.php @@ -32,7 +32,7 @@ public function format(?string $message): ?string public function getStyle(string $name): OutputFormatterStyleInterface { // to comply with the interface we must return a OutputFormatterStyleInterface - return $this->style ?? $this->style = new NullOutputFormatterStyle(); + return $this->style ??= new NullOutputFormatterStyle(); } /** diff --git a/src/Symfony/Component/Console/Helper/Dumper.php b/src/Symfony/Component/Console/Helper/Dumper.php index 76b76c0a4047d..29263d853e26f 100644 --- a/src/Symfony/Component/Console/Helper/Dumper.php +++ b/src/Symfony/Component/Console/Helper/Dumper.php @@ -34,10 +34,10 @@ public function __construct(OutputInterface $output, CliDumper $dumper = null, C if (class_exists(CliDumper::class)) { $this->handler = function ($var): string { - $dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); + $dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); $dumper->setColors($this->output->isDecorated()); - return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true)); + return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true)); }; } else { $this->handler = function ($var): string { diff --git a/src/Symfony/Component/Console/Helper/Helper.php b/src/Symfony/Component/Console/Helper/Helper.php index 01e39a9b38804..44e3199a987c8 100644 --- a/src/Symfony/Component/Console/Helper/Helper.php +++ b/src/Symfony/Component/Console/Helper/Helper.php @@ -45,7 +45,7 @@ public function getHelperSet(): ?HelperSet */ public static function width(?string $string): int { - $string ?? $string = ''; + $string ??= ''; if (preg_match('//u', $string)) { return (new UnicodeString($string))->width(false); @@ -64,7 +64,7 @@ public static function width(?string $string): int */ public static function length(?string $string): int { - $string ?? $string = ''; + $string ??= ''; if (preg_match('//u', $string)) { return (new UnicodeString($string))->length(); @@ -82,7 +82,7 @@ public static function length(?string $string): int */ public static function substr(?string $string, int $from, int $length = null): string { - $string ?? $string = ''; + $string ??= ''; if (false === $encoding = mb_detect_encoding($string, null, true)) { return substr($string, $from, $length); diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 4e90c812e2cbd..a9585de17e5ca 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -45,7 +45,7 @@ class ArgvInput extends Input public function __construct(array $argv = null, InputDefinition $definition = null) { - $argv = $argv ?? $_SERVER['argv'] ?? []; + $argv ??= $_SERVER['argv'] ?? []; // strip the application name array_shift($argv); diff --git a/src/Symfony/Component/Console/Input/InputArgument.php b/src/Symfony/Component/Console/Input/InputArgument.php index f89fa33c97eaf..143e4b10ae71f 100644 --- a/src/Symfony/Component/Console/Input/InputArgument.php +++ b/src/Symfony/Component/Console/Input/InputArgument.php @@ -105,8 +105,6 @@ public function setDefault(string|bool|int|float|array $default = null) /** * Returns the default value. - * - * @return string|bool|int|float|array|null */ public function getDefault(): string|bool|int|float|array|null { diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php index 613af20353f08..f9d74a8961ce2 100644 --- a/src/Symfony/Component/Console/Input/InputOption.php +++ b/src/Symfony/Component/Console/Input/InputOption.php @@ -187,8 +187,6 @@ public function setDefault(string|bool|int|float|array $default = null) /** * Returns the default value. - * - * @return string|bool|int|float|array|null */ public function getDefault(): string|bool|int|float|array|null { diff --git a/src/Symfony/Component/Console/Question/Question.php b/src/Symfony/Component/Console/Question/Question.php index 7d5e3accb0c05..f25e938a83197 100644 --- a/src/Symfony/Component/Console/Question/Question.php +++ b/src/Symfony/Component/Console/Question/Question.php @@ -52,8 +52,6 @@ public function getQuestion(): string /** * Returns the default answer. - * - * @return string|bool|int|float|null */ public function getDefault(): string|bool|int|float|null { @@ -154,7 +152,7 @@ public function setAutocompleterValues(?iterable $values): static } elseif ($values instanceof \Traversable) { $valueCache = null; $callback = static function () use ($values, &$valueCache) { - return $valueCache ?? $valueCache = iterator_to_array($values, false); + return $valueCache ??= iterator_to_array($values, false); }; } else { $callback = null; diff --git a/src/Symfony/Component/Console/Resources/completion.fish b/src/Symfony/Component/Console/Resources/completion.fish new file mode 100644 index 0000000000000..6566c58a3f9ea --- /dev/null +++ b/src/Symfony/Component/Console/Resources/completion.fish @@ -0,0 +1,29 @@ +# This file is part of the Symfony package. +# +# (c) Fabien Potencier +# +# For the full copyright and license information, please view +# https://symfony.com/doc/current/contributing/code/license.html + +function _sf_{{ COMMAND_NAME }} + set sf_cmd (commandline -o) + set c (math (count (commandline -oc))) - 1) + + set completecmd "$sf_cmd[1]" "_complete" "-sfish" "-S{{ VERSION }}" + + for i in $sf_cmd + if [ $i != "" ] + set completecmd $completecmd "-i$i" + end + end + + set completecmd $completecmd "-c$c" + + set sfcomplete ($completecmd) + + for i in $sfcomplete + echo $i + end +end + +complete -c '{{ COMMAND_NAME }}' -a '(_sf_{{ COMMAND_NAME }})' -f diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php index 58640ab5f0f3d..5e0aabb6e7b4d 100644 --- a/src/Symfony/Component/Console/Style/SymfonyStyle.php +++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php @@ -59,7 +59,7 @@ public function __construct(InputInterface $input, OutputInterface $output) /** * Formats a message as a block of text. */ - public function block(string|array $messages, ?string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true) + public function block(string|array $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true) { $messages = \is_array($messages) ? array_values($messages) : [$messages]; diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index a4fe5b8e4638a..619009efe29cb 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -883,6 +883,9 @@ public function testRenderExceptionLineBreaks() $this->assertStringMatchesFormatFile(self::$fixturesPath.'/application_renderexception_linebreaks.txt', $tester->getDisplay(true), '->renderException() keep multiple line breaks'); } + /** + * @group transient-on-windows + */ public function testRenderAnonymousException() { $application = new Application(); @@ -906,6 +909,9 @@ public function testRenderAnonymousException() $this->assertStringContainsString('Dummy type "class@anonymous" is invalid.', $tester->getDisplay(true)); } + /** + * @group transient-on-windows + */ public function testRenderExceptionStackTraceContainsRootException() { $application = new Application(); diff --git a/src/Symfony/Component/Console/Tests/Command/CompleteCommandTest.php b/src/Symfony/Component/Console/Tests/Command/CompleteCommandTest.php index 189928897cc7c..74caa246c7b03 100644 --- a/src/Symfony/Component/Console/Tests/Command/CompleteCommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/CompleteCommandTest.php @@ -47,7 +47,7 @@ public function testRequiredShellOption() public function testUnsupportedShellOption() { - $this->expectExceptionMessage('Shell completion is not supported for your shell: "unsupported" (supported: "bash").'); + $this->expectExceptionMessage('Shell completion is not supported for your shell: "unsupported" (supported: "bash", "fish").'); $this->execute(['--shell' => 'unsupported']); } diff --git a/src/Symfony/Component/Console/Tests/Command/DumpCompletionCommandTest.php b/src/Symfony/Component/Console/Tests/Command/DumpCompletionCommandTest.php index de8a3d4a60a3a..b50e42b160378 100644 --- a/src/Symfony/Component/Console/Tests/Command/DumpCompletionCommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/DumpCompletionCommandTest.php @@ -23,7 +23,7 @@ public function provideCompletionSuggestions() { yield 'shell' => [ [''], - ['bash'], + ['bash', 'fish'], ]; } } diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_1.json b/src/Symfony/Component/Console/Tests/Fixtures/application_1.json index 280a4247eb39f..2cd6ee9618f79 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_1.json +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_1.json @@ -89,7 +89,7 @@ "accept_value": true, "is_value_required": true, "is_multiple": false, - "description": "The shell type (\"bash\")", + "description": "The shell type (\"bash\", \"fish\")", "default": null }, "current": { diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_1.xml b/src/Symfony/Component/Console/Tests/Fixtures/application_1.xml index 5a17229343fcf..0f78ec5d36448 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_1.xml +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_1.xml @@ -10,7 +10,7 @@